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…
June 28, 2010 at 5:35 pm |
my @positives = 1..*; is now lazy. 🙂
Pm