File Coverage

blib/lib/Perlude/Lazy.pm
Criterion Covered Total %
statement 113 114 99.1
branch 52 56 92.8
condition 10 13 76.9
subroutine 34 34 100.0
pod 0 15 0.0
total 209 232 90.0


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