A generator object for Perl 5
I have recently started a new job and it has forced me to learn more Python than I have ever had need to learn. I decided that I should take this as an opportunity to learn, and as Miyagawa-san has often done, steal when possible.
One thing that fascinated me is Python’s yield
or generator pattern. In this pattern, you can make a function (or my case an object) which implements a lazy iterator returning a value (or possibly values (see below)) without leaving the while
loop that generates them.
#!/usr/bin/env perl
use strict;
use warnings;
use Generator::Object;
my $evens = generator {
my $i = 0;
while (1) {
$_->yield($i);
$i += 2;
}
};
use feature 'say';
say $evens->next; # 0
say $evens->next; # 2
say $evens->next; # 4
In the above example the generator body maintains an infinite while loop. It starts with the first call to next
, and yields a value when the yield
method is called, and at that point it waits for subsequent calls to next
.
I have been having a hard time coming up with a usage that Perl doesn’t make possible given better closure support and state
variables, but certainly the generator pattern makes for clean code in some cases. Its claim to fame is to help reduce the amount of information held in memory in certain algorithms or when reading data (though Perl already has mechanisms for this in many/most cases). Still I find it a nice pattern and one that I hope to use more.
As I have already shown, I have implemented a generator/yield pattern in Perl called Generator::Object. Under the hood it uses Coro as the pattern requires coroutines to support it. Indeed there were a couple modules that already did this, but their interface were not to my liking.
In my example you see my first major difference, the generator is an object and not a function. Further I localize the topic variable $_
to be the generator object itself when next
is called on it. This makes it easy to access the methods on the object from within the generator. I have used this to make the generator more Perlish. For example, the object has a wantarray
method which conveys the context in which next
was called and thus the yield
method may respond appropriately. Planned is the ability to pass arguments in to the generator and possibly even to the next
method (think an aggregator type generator).
I hope you can enlighten me about cases in which the generator/yield pattern has marked differences, but until then, it still can be used to separate logic more cleanly if nothing else and I found it to be a fun toy project.
Feature request:
This would allow:
… which is the most common way people do iterators in Perl.
Oh yes, this is planned too. In fact I had meant to do it before releasing and then it slipped my mind. Thanks for the reminder!
To be truly idiomatic, you could overload <…>, though that may be of questionable taste.
Have a look at Perlude.
Another vote for overloading ‘<>’