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.
Take #1: One way gather
behaves lazily
gather
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
Take #2: A second way gather
behaves lazily
Some 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.
Take #3: One way gather
behaves eagerly
Some 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.
Take #4: A second way gather
behaves eagerly
Some 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
.
Take #5: What's happening in your examples
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.