File Coverage

blib/lib/List/Lazy.pm
Criterion Covered Total %
statement 159 159 100.0
branch 35 38 92.1
condition 20 24 83.3
subroutine 36 36 100.0
pod 13 14 92.8
total 263 271 97.0


line stmt bran cond sub pod time code
1             package List::Lazy;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Generate lists lazily
4             $List::Lazy::VERSION = '0.4.0';
5              
6 9     9   1572077 use Moo;
  9         67291  
  9         70  
7 9     9   16707 use MooX::HandlesVia;
  9         113661  
  9         70  
8              
9 9     9   5409 use Clone qw/ clone /;
  9         4963  
  9         652  
10              
11 9     9   127 use 5.22.0;
  9         32  
12              
13 9     9   4366 use experimental 'signatures', 'postderef';
  9         36438  
  9         51  
14              
15 9     9   9110 use List::MoreUtils;
  9         145808  
  9         67  
16 9     9   10784 use Carp;
  9         36  
  9         24351  
17              
18             *list_before = *List::MoreUtils::before;
19              
20             extends 'Exporter::Tiny';
21              
22             sub _exporter_validate_opts {
23 8     8   2083 my $into = $_[1]->{into};
24 8 50       956 eval qq{
25             *${into}::a = *::a;
26             *${into}::b = *::b;
27             1;
28             } or die $@;
29             }
30              
31             our @EXPORT_OK = qw/ lazy_list lazy_range lazy_fixed_list /;
32              
33             our $MAX_NEXT = 10_000;
34              
35 11     11   15 sub _lazy_list ($generator,$state=undef) {
  11         21  
  11         16  
  11         12  
36 11         289 return List::Lazy->new(
37             generator => $generator,
38             state => $state,
39             );
40             }
41              
42 11     11 1 249 sub lazy_list :prototype(&@) { goto &_lazy_list }
43              
44 8     8   14 sub _lazy_range ($min,$max,$step=1) {
  8         11  
  8         10  
  8         12  
  8         26  
45 8 100   26   32 my $it = ref $step ? $step : sub { $_ + $step };
  26         34  
46              
47             return scalar lazy_list {
48 33 100 100 33   87 return if defined $max and $_ > $max;
49 29         32 my $current = $_;
50 29         46 $_ = $it->();
51 29         66 return $current;
52 8         32 } $min;
53             }
54              
55              
56 8     8 1 456105 sub lazy_range :prototype($$@) { goto &_lazy_range }
57              
58             sub lazy_fixed_list {
59             return List::Lazy->new(
60             _next => [ @_ ],
61 22     22   40 generator => sub { return () },
62 22     22 1 1320727 );
63             }
64              
65             has generator => (
66             is => 'ro',
67             required => 1,
68             );
69              
70             has state => (
71             is => 'rw'
72             );
73              
74             has _next => (
75             is => 'rw',
76             handles_via => 'Array',
77             handles => {
78             has_next => 'count',
79             shift_next => 'shift',
80             push_next => 'push',
81             _all_next => 'elements',
82             },
83             default => sub { [] },
84             );
85              
86             has is_done => (
87             is => 'rw',
88             default => sub { 0 },
89             );
90              
91 10166     10166 0 436629 sub generate_next($self) {
  10166         13173  
  10166         11125  
92 10166         18912 local $_ = $self->state;
93              
94 10166         23252 my @values = $self->generator->();
95 10166         32080 $self->state($_);
96              
97 10166 100       16517 $self->is_done(1) unless @values;
98              
99 10166         172283 return @values;
100             }
101              
102 170     170 1 307 sub next($self,$num=1) {
  170         245  
  170         249  
  170         204  
103 170         233 my @returns;
104              
105 170         295 my $count = 0;
106              
107 170 50 100     762 croak "next called in scalar context with \$num = $num"
      66        
108             if defined wantarray and not wantarray and $num != 1;
109              
110 170   100     689 while( @returns < $num and not $self->is_done ) {
111 10261 100       462707 if( ++$count > $MAX_NEXT ) {
112 1         189 die <<"END";
113             Number of items returned by the lazy list exceeds MAX_NEXT ($MAX_NEXT).
114              
115             If this is what you want, you can increase this limit via \$Lazy::List::MAX_NEXT
116             END
117             }
118              
119 10260 100       164692 $self->push_next( $self->generate_next )
120             unless $self->has_next;
121              
122 10260 100       583457 next unless $self->has_next;
123              
124 10220 100       447107 if( ref $self->_next->[0] eq 'List::Lazy' ) {
125 9         13 my $list = $self->_next->[0];
126 9         12 push @returns, $list->next;
127 9 100       57 $self->shift_next if $list->is_done;
128             }
129             else {
130 10211         158795 push @returns, $self->shift_next;
131             }
132             }
133              
134 169 100       8151 return wantarray ? @returns : $returns[0];
135             }
136              
137 13     13 1 154 sub all ($self) {
  13         21  
  13         35  
138 13         44 return $self->next(1E99);
139             }
140              
141 1     1 1 13 sub reduce($self,$reducer,$reduced=undef) {
  1         4  
  1         3  
  1         13  
  1         2  
142 1   33     9 $reduced //= $self->next;
143              
144 1         4 while( my $next = $self->next ) {
145 9         21 local ( $::a, $::b ) = ( $reduced, $next );
146 9         24 $reduced = $reducer->();
147             }
148              
149 1         11 return $reduced;
150             }
151              
152 9     9 1 35 sub map($self,$map) {
  9         12  
  9         13  
  9         16  
153             return List::Lazy->new(
154             state => $self->_clone,
155             generator => sub {
156 32     32   66 while( my @next = $_->next ) {
157 35         55 @next = map { $map->() } @next;
  35         130  
158 35 100       160 return @next if @next;
159             }
160 3         8 return;
161             },
162 9         24 );
163             }
164              
165 2     2 1 27 sub batch($self,$n) {
  2         6  
  2         5  
  2         4  
166             return List::Lazy->new(
167             state => [ $self->_clone, [] ],
168             generator => sub {
169 8     8   32 my $stash = $_->[1];
170              
171 8         23 while( my @next = $_->[0]->next ) {
172 10         20 push @$stash, @next;
173 10 100       29 if( @$stash >= $n ) {
174 4         19 return [ splice @$stash, 0, $n ];
175             }
176             }
177              
178 4 100       88 return @$stash ? [ splice @$stash ] : ();
179             },
180 2         21 );
181             };
182              
183 2     2 1 22 sub grep($self,$filter) {
  2         4  
  2         3  
  2         3  
184 14 100   14   26 $self->map(sub{ $filter->() ? $_ : () })
185 2         10 }
186              
187 4     4 1 1193 sub spy($self,$sub=undef) {
  4         8  
  4         9  
  4         12  
188 4   66 2   44 $sub ||= sub { carp $_ };
  2         314  
189 4     12   19 $self->map(sub{ $sub->(); $_ } );
  12         58  
  12         775  
190             }
191              
192 27     27   41 sub _clone($self,%args) {
  27         50  
  27         44  
  27         31  
193 27         897 return List::Lazy->new(
194             state => clone( $self->state ),
195             generator => $self->generator,
196             _next => [ $self->_next->@* ],
197             %args
198             );
199             }
200              
201 1     1 1 16 sub until($self,$condition) {
  1         2  
  1         3  
  1         13  
202 1         3 my $done;
203             return List::Lazy->new(
204             state => $self->_clone,
205             generator => sub {
206 11 50   11   27 return () if $done;
207 11         32 my @next = $_->next;
208 11         135 my @filtered = list_before( sub { $condition->() }, @next );
  11         66  
209 11         100 $done = @filtered < @next;
210 11         70 return @filtered;
211             },
212 1         4 );
213             }
214              
215 6     6 1 37 sub append($self,@list) {
  6         11  
  6         13  
  6         16  
216              
217 6         19 my @state = ( $self->_clone );
218              
219 6         41 while(@list) {
220 13 100       65 if( ref $list[0] eq 'List::Lazy' ) {
221 9         18 push @state, (shift @list)->_clone;
222             }
223             else {
224 4         9 my @lazy;
225 4   100     67 push @lazy, shift @list while @list and ref $list[0] ne 'List::Lazy';
226 4         12 push @state, lazy_fixed_list @lazy;
227             }
228             }
229              
230             return List::Lazy->new(
231             state => \@state,
232             generator => sub {
233 52     52   102 while(@$_) {
234 71   100     351 shift @$_ while @$_ and $_->[0]->is_done;
235 71 100       148 last unless @$_;
236 65         133 my @next = $_->[0]->next;
237 65 100       172 return @next if @next;
238             }
239 6         11 return ();
240             },
241 6         132 );
242             }
243              
244 2     2 1 13 sub prepend( $self, @list ) {
  2         19  
  2         5  
  2         4  
245 2     2   39 List::Lazy->new(done => 1, generator => sub {})->append(
246             @list, $self,
247             );
248             }
249              
250             1;
251              
252             __END__