About Laziness [ RAKU ]
Asked Answered
D

1

7

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 ???

Demoniac answered 17/1, 2020 at 14:10 Comment(1)
"In Raku documentation it is stated that gather-take constructs are being lazy evaluated." They can be. But not necessarily. Please provide a link to which particular doc you're reading.Brynnbrynna
B
10

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.

Brynnbrynna answered 17/1, 2020 at 15:52 Comment(1)
FWIW, I find say .is-lazy with 1..Inf clearer to read, as it won't make you think it might iterate over the range,Pentecostal

© 2022 - 2024 — McMap. All rights reserved.