Data-Enumerable-Lazy
view release on metacpan or search on metacpan
NAME
Data::Enumerable::Lazy - Lazy generator + enumerable for Perl5.
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]
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
EXAMPLES
A basic range
This example implements a range generator from $from until $to. In order
to generate this range we define 2 callbacks: `on_has_next()' and
`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()'
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
$self->{_prev_} = $next;
( run in 0.635 second using v1.01-cache-2.11-cpan-39bf76dae61 )