6
votes

I find that one of the most interesting features of both Haskell and Perl6 is the ability to defer calculating values until they are actually needed.

Perl5 on the other hand likes to do everything immediately, but from what I can tell, contains all of the necessary primitives for lazy evaluation. Those are:

  • taking a reference to @_ in a subroutine creates an array reference that is aliased to the identifiers in its argument list, even if some of those identifiers do not contain values yet.
  • returning overloaded / tied objects from such subroutines that hold \@_ internally, and then dereference it when needed. (and there are various CPAN modules that abstract away the tie/overload details)

I've been experimenting with various lazy programming techniques in Perl (I have a module in the works that implements a fair bit of the Haskell Prelude in Perl5 (things like co-recursion: $_ = list 0, 1, zipWith {&sum} $_, tail $_ for my $fibs; to define the Fibonacci sequence are already working)). But I have a feeling that there are some subtle bugs hiding in the code that may manifest when the functions are used in larger expressions / programs.

So I am wondering if there are any good examples (CPAN / blogs / books) that anyone knows of that employ Haskell/Perl6 like laziness in Perl5? In particular, I would like to read through any code of significant size that employs this type of laziness.

I would also be interested to know if anyone has run into any gotchas or intractable problems surrounding the implementation of lazy evaluation in Perl 5.

3
I'm assuming that you've read Higher Order Perl. It has a lot of material about iterators, infinite streams, and laziness.Michael Carman

3 Answers

9
votes

Higher-Order Perl (freely available online) has a chapter called "Infinite Streams". Maybe that's a good starting point.

6
votes

Well, Moose does this with lazy loading of attributes:

Default and builder methods
Attributes can have default values, and Moose provides two ways to specify that default.
["default" and "builder" options described...]

Laziness
Moose lets you defer attribute population by making an attribute "lazy":

has 'size' => (
    is      => 'ro',
    lazy    => 1,
    builder => '_build_size',
);

When "lazy" is true, the default is not generated until the reader method is called, rather than at object construction time. There are several reasons you might choose to do this.

First, if the default value for this attribute depends on some other attributes, then the attribute must be "lazy". During object construction, defaults are not generated in a predictable order, so you cannot count on some other attribute being populated when generating a default.

Second, there's often no reason to calculate a default before it's needed. Making an attribute "lazy" lets you defer the cost until the attribute is needed. If the attribute is never needed, you save some CPU time.

We recommend that you make any attribute with a builder or non-trivial default "lazy" as a matter of course.

4
votes

I think the biggest issue is a "Clash of Magics". When you're trying to do something you think is spiffy and some other module author is trying to something they think is spiffy and the result is indeterminacy. Whose magic wins?

As one can always peek behind the magic of another variable, or even inadvertently disenchant it, magics aren't ever bulletproof.

If you could achieve 100% orthogonality through exhaustive case testing, you might be able to rest easier...maybe.