## Laziness in Perl 6: Much Easier

Just saw a nice blog post from Tinydot on using laziness in Perl 5 (as compared to Haskell). Of course, laziness is also a key feature of Perl 6, so I thought it would be fun to rewrite the code for Rakudo.

First, here’s the Haskell version from that post:

positives = [0..]
is_even x = x `mod` 2 == 0
evens = filter is_even

main = mapM_ print
[ ("10 positives", take 10 positives)
, ("10 evens" , take 10 (evens positives) )
, ("evens < 10" , takeWhile (< 10) (evens positives) )
]

Here’s my Perl 6 translation (works with current Rakudo):

sub is_even(\$x) { \$x !% 2 };
sub evens(@x) { @x.grep(&is_even); }

sub take-while(Mu \$test, \$data) {
gather {
for \$data.list {
if \$_ ~~ \$test {
take \$_;
} else {
last;
}
}
}
}

say "10 positives ", ~(1..*).list.munch(10);
say "10 evens ", ~evens(1..*).munch(10);
say "evens < 10 ", ~take-while(* < 10, evens(1..*));

(Note that I took 1 to infinity as the positives — I think the Haskell takes 0 to infinity.)

I tried using @positives = 1..*, but apparently that usage isn’t lazy yet. Perl 6 doesn’t have a take-while method (so far as I know), so I had to write one — very straightforward. Note that this implementation is fully lazy. Other than those two things, and a little Haskell weirdness for output, this is basically a line-for-line translation of the Haskell version.

A word about the use of Mu in the take-while definition. That’s so that any kind of test can be passed, including junctions: you could say something like take-while(2|4|6|8, evens(1..*)). It is every bit as powerful as the built in grep method. (In fact, I actually copied grep’s source and modified it to get this sub.)

Update: Masak++ points out that there is some question whether @positives = 1..* is supposed to be lazy. It may be that the proper lazy way is @positives <== 1..*. This is an area of the Spec which is still in a bit of flux…