File Coverage

blib/lib/Perlude/Lazy.pm
Criterion Covered Total %
statement 119 120 99.1
branch 52 56 92.8
condition 10 13 76.9
subroutine 36 36 100.0
pod 0 15 0.0
total 217 240 90.4


line stmt bran cond sub pod time code
1             package Perlude::Lazy;
2 11     11   124171 use strict;
  11         17  
  11         304  
3 11     11   32 use warnings;
  11         13  
  11         221  
4 11     11   117 use 5.10.0;
  11         30  
  11         384  
5 11     11   47 use Carp qw< croak >;
  11         15  
  11         651  
6 11     11   42 use Exporter qw< import >;
  11         10  
  11         546  
7             our $VERSION = '0.0';
8             our @EXPORT = qw<
9              
10             enlist unfold
11             fold
12             takeWhile take drop
13             filter apply
14             now
15             cycle range
16             tuple
17             lines
18             concat
19              
20             >;
21              
22 11     11   36 use Carp;
  11         14  
  11         10955  
23              
24             # End-of-list value: always return itself, with no data
25             {
26             my $NIL;
27             $NIL = sub { $NIL };
28 55     55 0 1386 sub NIL() { $NIL }
29             }
30              
31             # interface with the Perl world
32             sub enlist (&) {
33 69     69 0 3559 my ($i) = @_;
34 69         59 my ( $l, @b );
35             $l = sub {
36 40290 100   40290   57662 if (@_) {
37 44         43 my $n = shift;
38 44 100       127 return ( $l, @b[ 0 .. $n - 1 ] ) if @b >= $n; # there's enough
39 15         25 push @b, my @v = $i->(); # need more
40 15   100     60 push @b, @v = $i->() while @b < $n && @v; # MOAR
41 15 100       320 return ( $l, @b < $n ? @b : @b[ 0 .. $n - 1 ] ); # give it a peek
42             }
43             else {
44 40246 100       44625 return ( $l, shift @b ) if @b; # use the buffer first
45 40228         39853 push @b, $i->(); # obtain more items
46 40228 100       115502 return @b ? ( $l, shift @b ) : NIL;
47             }
48 69         1271 };
49             }
50              
51             sub concat {
52 1     1 0 3 my ($l, @ls)= @_;
53 1         2 my @v;
54             my $r;
55             $r = sub {
56 11     11   17 while ($l) {
57 12         11 ( $l, @v ) = $l->();
58 12 100       31 return ($r,@v) if @v;
59 2         5 $l = shift @ls;
60             }
61 1         3 };
62 1         4 $r
63             }
64              
65             sub unfold (@) {
66 45     45 0 12169 my @array = @_;
67 45 100   185   126 enlist { @array ? shift @array : () };
  185         280  
68             }
69              
70             sub fold ($) {
71 84     84 0 75 my ($l) = @_;
72 84         66 my @v;
73 84 100       144 unless (wantarray) {
74 23 100       52 if ( defined wantarray ) {
75 6         5 my $n = 0;
76 6         376 $n += @v while 1 < ( ( $l, @v ) = $l->() );
77 6         17 return $n;
78             }
79             else {
80             # The real lazy one: when called in scalar context, values are
81             # ignored:
82             # undef while defined ( $l = $l->() );
83             # But producers must be able to handle that
84             # So keep that for later and use the eager implementation for now
85 17         22 undef while 1 < ( ( $l, @v ) = $l->() );
86 17         21 return;
87             }
88             }
89 61         37 my @r;
90 61         78 push @r, @v while 1 < ( ( $l, @v ) = $l->() );
91 61         2537 @r;
92             }
93              
94             # stream consumers (lazy)
95             sub takeWhile (&$) {
96 5     5 0 1491 my ( $cond, $l ) = @_;
97 5         5 my $m;
98             $m = sub {
99 10 100   10   33 1 < ( ( $l, my @v ) = $l->() ) or return ($l);
100 9 100       17 return $cond->() ? ( $m, @v ) : ( sub { ( $l, @v ) } ) for @v;
  0         0  
101 5         28 };
102             }
103              
104             sub filter (&$) {
105 3     3 0 617 my ( $cond, $l ) = @_;
106 3         3 my $m;
107             $m = sub {
108 10006     10006   5472 while (1) {
109 20009 100       35429 1 < ( ( $l, my @v ) = $l->() ) or return ($l);
110 20007   100     26153 $cond->() and return ($m, @v) for @v;
111             }
112 3         15 };
113             }
114              
115             sub take ($$) {
116 71     71 0 4060 my ( $n, $l ) = @_;
117 71         53 my $m;
118             $m = sub {
119 30206 100   30206   34200 $n-- > 0 or return ($l);
120 30174 100       25687 1 < ( ( $l, my @v ) = $l->() ) or return ($l);
121 30135         113278 ( $m, @v );
122             }
123 71         650 }
124              
125             sub drop ($$) {
126 12     12 0 3208 my ( $n, $l ) = @_;
127 12         19 fold take $n, $l;
128 12         17 $l;
129             }
130              
131             sub apply (&$) {
132 2     2 0 48852 my ( $code, $l ) = @_;
133 2         3 my $m;
134             $m = sub {
135 10004 100   10004   8325 1 < ( ( $l, my @v ) = $l->() ) or return $l;
136 10003         12819 ( $m, map $code->(), @v );
137             }
138 2         13 }
139              
140             # stream consumers (exhaustive)
141             sub now (&$) {
142 4     4 0 1752 my ( $code, $l ) = @_;
143 4         3 my @b;
144 4         5 while (1) {
145 29 100       67 1 < ( ( $l, my @v ) = $l->() ) or return pop @b;
146 25         35 @b = map $code->(), @v;
147             }
148             }
149              
150             # stream generators
151             sub cycle (@) {
152 2 50   2 0 10 (my @ring = @_) or return NIL;
153 2         3 my $index = -1;
154 10010     10010   10078 enlist { $ring[ ( $index += 1 ) %= @ring ] }
155 2         8 }
156              
157             sub range ($$;$) {
158 14   33 14 0 42 my $begin = shift // croak "range begin undefined";
159 14         13 my $end = shift;
160 14   100     27 my $step = shift // 1;
161              
162 14 50       27 return NIL if $step == 0;
163              
164 14         12 $begin -= $step;
165 14         10 my $l;
166             return $l = defined $end
167             ? $step > 0
168 22 100   22   53 ? sub { ( ( $begin += $step ) <= $end ) ? ( $l, $begin ) : ($l) }
169 8 100   8   25 : sub { ( ( $begin += $step ) >= $end ) ? ( $l, $begin ) : ($l) }
170 14 100   15   474 : sub { ( $l, $begin += $step ) };
  15 100       30  
171             }
172              
173              
174             sub tuple ($$) {
175 6     6 0 2824 my ( $n, $l ) = @_;
176 6 100       318 croak "$n is not a valid parameter for tuple()" if $n <= 0;
177 3         3 my $m;
178             $m = sub {
179 9     9   10 $l = take $n, $l;
180 9         13 my (@r, @v);
181 9         9 push @r, @v while 1 < ( ( $l, @v ) = $l->() );
182 9 100       25 @r ? ( $m, \@r ) : ( $l )
183             }
184 3         14 }
185              
186             sub lines {
187             # private sub that coerce path to handles
188             state $fh_coerce = sub {
189 2     2   3 my $v = shift;
190 2 50       5 return $v if ref $v;
191 2         59 open my ($fh),$v;
192 2         5 $fh;
193 2     2 0 10747 };
194 2         6 my $fh = $fh_coerce->( pop );
195              
196             # only 2 forms accepted for the moment
197             # form 1: lines 'file'
198 2 100 66 4   10 @_ or return enlist { <$fh> // () };
  4         17  
199              
200             # confess if not 2nd form
201 1 50       3 $_[0] eq 'chomp' or confess 'cannot handle parameters ' , join ',', @_ ;
202              
203             # lines chomp => 'file'
204             enlist {
205 4 100   4   17 defined (my $v = <$fh>) or return;
206 3         4 chomp $v;
207 3         4 $v;
208             }
209              
210 1         5 }
211              
212             1;
213              
214             =head1 Perlude::Lazy
215              
216             An experimentation of implementing real lazy lists in Perl5.
217             For real world usecases, please use Perlude instead.
218              
219             =head1 SYNOPSIS
220              
221             Haskell prelude miss you when you write perl stuff? Perlude is a port of the
222             most common keywords. Some other keywords where added when there is no haskell
223             equivalent.
224              
225             Example: in haskell you can write
226              
227             nat = [0..]
228             is_even x = ( x `mod` 2 ) == 0
229             evens = filter is_even
230             main = mapM_ print
231             $ take 10
232             $ evens nat
233              
234             in perlude, the same code will be:
235              
236             use Perlude;
237             my $nat = enlist { state $x = 0; $x++ };
238             sub is_even { ($_ % 2) == 0 }
239             sub evens { filter {is_even} shift }
240             traverse {say} take 10, evens $nat
241              
242             =head1 FUNCTIONS
243              
244             all the Perlude documentation is relevent. just replace sub by enlist
245              
246             =cut
247