In Raku documentation it is stated that gather-take constructs are being lazy evaluated. In the following examples I have a hard time concluding about the laziness of the constructs:
say 'Iterate to Infinity is : ', (1 ... Inf).WHAT;
say 'gather is : ', gather {
take 0;
my ($last, $this) = 0, 1;
loop {
take $this;
($last, $this) = $this, $last + $this;
}
}.WHAT;
say '------------------------------------';
my @f1 = lazy gather {
take 0;
my ($last, $this) = 0, 1;
loop {
take $this;
($last, $this) = $this, $last + $this;
}
}
say '@f1 : ', @f1.WHAT;
say '@f1 is lazy : ', @f1.is-lazy;
say '------------------------------------';
my @f2 = 1 ... Inf;
say '@f2 : ', @f2.WHAT;
say '@f2 is lazy : ', @f2.is-lazy;
In the first case (assignement of a Seq to @f1) if we take away the "lazy" definition then the generated Sequence (with use of gather-take) is running forever (NOT lazy).
In the second case (assignement of a Seq to @f2) @f2 becomes lazy.
Why do we have a differentiation in behaviour? although we try to do the same thing: Assign a Seq to an array in a lazy way
Can someone clarify the matter ???
In Raku documentation it is stated that gather-take constructs are being lazy evaluated.
They can be. But not necessarily. They're perfectly happy to work either way.
In a key twist, if a gather
is asked if it's lazy, it returns False
. This is what results in the behaviour you see.
gather
behaves lazilygather
evaluates the next element in its sequence when it's needed. This is the classic definition of lazy evaluation, as described by Wikipedia:
lazy evaluation ... delays the evaluation of an expression until its value is needed
gather
behaves lazilySome constructs that consume sequences of values always consume them lazily. If the sequence they're consuming is a gather
, they demand its values lazily. In which case the gather
obliges, evaluating its sequence until it reaches a take
, and then yielding until the next value is demanded.
gather
behaves eagerlySome constructs that consume sequences of values always consume them eagerly. If a sequence they're consuming is a gather
, they demand its values eagerly. In which case the gather
obliges, and any laziness in it is moot.
gather
behaves eagerlySome constructs that consume sequences of values demand them either lazily or eagerly, based on the sequence's answer to an .is-lazy
call on it; if it returns True
, then its values are demanded lazily, otherwise they're demanded eagerly.
And here comes a critical twist: when .is-lazy
is called on a gather
construct, it returns False
.
say .is-lazy
for (gather { take 42 }), # False
(gather { loop { take 42 } }); # False
In your @f1 = gather ...
case, @f1
is being assigned a sequence that says it's not lazy. This is so even though it contains an infinite loop. @
sigil'd variables take that as a cue to eagerly assign the sequence -- and the code hangs.
The prefix lazy
creates a new Seq
that lazily draws from the expression on its right. It also returns True
if .is-lazy
is called on it:
say .is-lazy
for (lazy gather { take 42 }), # True
(lazy gather { loop { take 42 } }); # True
If an @
sigil'd variable is assigned a value that returns True
for a call to .is-lazy
, the assignment, and the variable, are lazy. So the code @f1 = lazy gather ...
works fine.
Finally, the sequence (1...Inf)
knows it's lazy, and tells the world it is, without needing a prefix lazy
:
say .is-lazy with (1 ... Inf) # True
So assigning that also works fine, with or without lazy
.
In summary, an @
sigil'd variable gains elements lazily if a Seq
assigned to it says it's lazy, and eagerly otherwise.
You didn't ask about this, but another scenario is assigning or binding a Seq
to a $
sigil'd variable, or a sigil free identifier.
As with a @
sigil'd variable, calling .is-lazy
on the $
sigil'd variable or sigil free identifier will return True
or False
in accord with the assigned/bound Seq
.
But then, regardless of whether that .is-lazy
returns True
or False
, the Seq
will still be iterated lazily.