lazy-evaluationrakuiterablelazy-sequences

Getting the last element of a lazy Seq in Raku


I want to get the last element of a lazy but finite Seq in Raku, e.g.:

my $s = lazy gather for ^10 { take $_ };

The following don't work:

say $s[* - 1];
say $s.tail;

These ones work but don't seem too idiomatic:

say (for $s<> { $_ }).tail;
say (for $s<> { $_ })[* - 1];

What is the most idiomatic way of doing this while keeping the original Seq lazy?


Solution

  • Drop the lazy

    Lazy sequences in Raku are designed to work well as is. You don't need to emphasize they're lazy by adding an explicit lazy.

    If you add an explicit lazy, Raku interprets that as a request to block operations such as .tail because they will almost certainly immediately render laziness moot, and, if called on an infinite sequence, or even just a sufficiently large one, hang or OOM the program.

    So, either drop the lazy, or don't invoke operations like .tail that will be blocked if you do.

    Expanded version of my original answer

    As noted by @ugexe, the idiomatic solution is to drop the lazy.

    Quoting my answer to the SO About Laziness:

    if a gather is asked if it's lazy, it returns False.

    Aiui, something like the following applies:

    I think this approach works well, but it doc and error messages mentioning "lazy" may not have caught up with the shift made in 2015. So:

    Further discussion

    the docs ... say “If you want to force lazy evaluation use the lazy subroutine or method. Binding to a scalar or sigilless container will also force laziness.”

    Yes. Aiui this is correct.

    [which] sounds like it implies “my $x := lazy gather { ... } is the same as my $x := gather { ... }”.

    No.

    An explicit lazy statement prefix or method adds emphasis to laziness, and Raku interprets that to mean it ought block operations like .tail in case they hang the program.

    In contrast, binding to a variable alters neither emphasis nor deemphasis of laziness, merely relaying onward whatever the bound producer datatype/instance has chosen to convey via .is-lazy.

    not only in connection with gather but elsewhere as well

    Yes. It's about the result of .is-lazy:

    my $x =      (1, { .say; $_ + 1 } ... 1000);
    my $y = lazy (1, { .say; $_ + 1 } ... 1000);
    

    both act lazily ... but $x.tail is possible while $y.tail is not.

    Yes.

    An explicit lazy statement prefix or method forces the answer to .is-lazy to be True. This signals to a consumer that cares about the dangers of laziness that it should become cautious (eg rejecting .tail etc.).

    (Conversely, an eager statement prefix or method can be used to force the answer to .is-lazy to be False, making timid consumers accept .tail etc calls.)

    I take from this that there are two kinds of laziness in Raku, and one has to be careful to see which one is being used where.

    It's two kinds of what I'll call consumption guidance:

    It's not so much that there's a need to be careful about which of these two kinds is in play, but if one wants to call operations like tail, then one may need to enable that by inserting an eager or removing a lazy, and one must take responsibility for the consequences:

    What I would say is that the error messages and/or doc may well need to be improved.