Data-Enumerable-Lazy
view release on metacpan or search on metacpan
lib/Data/Enumerable/Lazy.pm view on Meta::CPAN
package Data::Enumerable::Lazy;
use 5.18.2;
use strict;
use warnings;
our $VERSION = '0.032';
=pod
=head1 NAME
Data::Enumerable::Lazy - Lazy generator + enumerable for Perl5.
=head1 SYNOPSIS
A basic lazy range implementation picking even numbers only:
my ($from, $to) = (0, 10);
my $current = $from;
my $tream = Data::Enumerable::Lazy->new({
on_has_next => sub { $current <= $to },
on_next => sub { shift->yield($current++) },
})->grep(sub{ shift % 2 == 0 });
$tream->to_list(); # generates: [0, 2, 4, 6, 8, 10]
=head2 DESCRIPTION
This library is another one implementation of a lazy generator + enumerable
for Perl5. It might be handy if the elements of the collection are resolved on
the flight and the iteration itself should be hidden from the end users.
The enumerables are single-pass composable calculation units. What it means:
An enumerable is stateful, once it reached the end of the sequence, it will
not rewind to the beginning unless explicitly forced to.
Enumerables are composable: one enumerable might be an extension of another by
applying some additional logic. Enumerables resolve steps on demand, one by one.
A single step might return another enumerable (micro batches). The library
flattens these enumerables, so for the end user this looks like a single
continuous sequence of elements.
[enumerable.has_next] -> [_buffer.has_next] -> yes -> return true
-> no -> result = [enumerable.on_has_next] -> return result
[enumerable.next] -> [_buffer.has_next] -> yes -> return [_buffer.next]
-> no -> result = [enumerable.next] -> [enumerable.set_buffer(result)] -> return result
=head1 EXAMPLES
=head2 A basic range
This example implements a range generator from $from until $to. In order to
generate this range we define 2 callbacks: C<on_has_next()> and C<on_next()>.
The first one is used as point of truth whether the sequence has any more
non-iterated elements, and the 2nd one is here to return the next element in
the sequence and the one that changes the state of the internal sequence
iterator.
sub basic_range {
my ($from, $to) = @_;
$from <= $to or die '$from should be less or equal $to';
my $current = $from;
Data::Enumerable::Lazy->new({
on_has_next => sub {
return $current <= $to;
},
on_next => sub {
my ($self) = @_;
return $self->yield($current++);
},
});
}
on_has_next() makes sure the current value does not exceed $to value, and
on_next() yields the next value of the sequence. Note the yield method.
An enumerable developer is expected to use this method in order to return
the next step value. This method does some internal bookkeeping and smart
caching.
Usage:
# We initialize a new range generator from 0 to 10 including.
my $range = basic_range(0, 10);
# We check if the sequence has elements in it's tail.
while ($range->has_next) {
# In this very line the state of $range is being changed
say $range->next;
}
is $range->has_next, 0, '$range has been iterated completely'
is $range->next, undef, 'A fully iterated sequence returns undef on next()'
=head2 Prime numbers
Prime numbers is an infinite sequence of natural numbers. This example
implements a very naive suboptimal prime number generator.
my $prime_num_stream = Data::Enumerable::Lazy->new({
# This is an infinite sequence
on_has_next => sub { 1 },
on_next => sub {
my $self = shift;
# We save the result of the previous step
my $next = $self->{_prev_} // 1;
LOOKUP: while (1) {
$next++;
# Check all numbers from 2 to sqrt(N)
foreach (2..floor(sqrt($next))) {
($next % $_ == 0) and next LOOKUP;
}
last LOOKUP;
}
# Save the result in order to use it in the next step
( run in 1.022 second using v1.01-cache-2.11-cpan-39bf76dae61 )