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?
Drop the
lazyLazy 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.tailbecause 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.tailthat 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:
Aiui, something like the following applies:
Some lazy sequence producers may be actually or effectively infinite. If so, calling
.tailetc on them will hang the calling program. Conversely, other lazy sequences perform fine when all their values are consumed in one go. How should Raku distinguish between these two scenarios?A decision was made in 2015 to let value producing datatypes emphasize or deemphasize their laziness via their response to an
.is-lazycall.Returning
Truesignals that a sequence is not only lazy but wants to be known to be lazy by consuming code that calls.is-lazy. (Not so much end-user code but instead built in consuming features such as@sigilled variables handling an assignment trying to determine whether or not to assign eagerly.) Built in consuming features take aTrueas a signal they ought block calls like.tail. If a dev knows this is overly conservative, they can add aneager(or remove an unneededlazy).Conversely, a datatype, or even a particular object instance, may return
Falseto signal that it does not want to be considered lazy. This may be because the actual behaviour of a particular datatype or instance is eager, but it might instead be that it is lazy technically, but doesn't want a consumer to block operations such as.tailbecause it knows they will not be harmful, or at least prefers to have that be the default presumption. If a dev knows better (because, say, it hangs the program), or at least does not want to block potentially problematic operations, they can add alazy(or remove an unneededeager).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:
If you've been confused by some doc about laziness, please search for doc issues with "lazy" in them, or "laziness", and add comments to existing issues, or file a new doc issue (perhaps linking to this SO answer).
If you've been confused by a Rakudo error message mentioning laziness, please search for Rakudo issues with "lazy" in them, and tagged
[LTA](which means "Less Than Awesome"), and add comments, or file a new Rakudo issue (with an[LTA]tag, and perhaps a link to this SO answer).Further discussion
Yes. Aiui this is correct.
No.
An explicit
lazystatement prefix or method adds emphasis to laziness, and Raku interprets that to mean it ought block operations like.tailin 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.Yes. It's about the result of
.is-lazy:Yes.
An explicit
lazystatement prefix or method forces the answer to.is-lazyto beTrue. This signals to a consumer that cares about the dangers of laziness that it should become cautious (eg rejecting.tailetc.).(Conversely, an
eagerstatement prefix or method can be used to force the answer to.is-lazyto beFalse, making timid consumers accept.tailetc calls.)It's two kinds of what I'll call consumption guidance:
Don't-tail-me If an object returns
Truefrom an.is-lazycall then it is treated as if it might be infinite. Thus operations like.tailare blocked.You-can-tail-me If an object returns
Falsefrom an.is-lazycall then operations like.tailare accepted.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 aneageror removing alazy, and one must take responsibility for the consequences:If the program hangs due to use of
.tail, well, DIHWIDT.If you suddenly consume all of a lazy sequence and haven't cached it, well, maybe you should cache it.
Etc.
What I would say is that the error messages and/or doc may well need to be improved.