What you're asking about ("get[ing] the last element of a lazy but finite Seq … while keeping the original Seq lazy") isn't possible. I don't mean that it's not possible with Raku – I mean that, in principle, it's not possible for any language that defines "laziness" the way Raku does with, for example, the is-lazy
method.
If particular, when a Seq is lazy in Raku, that "means that [the Seq's] values are computed on demand and stored for later use." Additionally, one of the defining features of a lazy iterable is that it cannot know its own length while remaining lazy – that's why calling .elems
on a lazy iterable throws an error:
my $s = lazy gather for ^10 { take $_ };
say $s.is-lazy; # OUTPUT: «True»
$s.elems; # THROWS: «Cannot .elems a lazy list onto a Seq»
Now, at this point, you might reasonably be thinking "well, maybe Raku doesn't know how long $s
is, but I can tell that it has exactly 10 elements in it." And you're not wrong – with that code, $s
is indeed guaranteed to have 10 elements. This means that, if you want to get the tenth (last) element of $s
, you can do so with $s[9]
. And accessing $s
's tenth element like that won't change the fact that $s.is-lazy
.
But, importantly, you can only do so because you know something "extra" about $s
, and that extra info undoes a good chunk of the reason you might want a list to be lazy in practice.
To see what I mean, consider a very similar Seq
my $s2 = lazy gather for ^10 { last if rand > .95; take $_ };
say $s2.is-lazy; # OUTPUT: «True»
Now, $s2
probably has 10 elements, but it might not – the only way to know is to iterate through it and find out. In turn, this means $s2[9]
does not jump to the tenth element the way $s[9]
did; it iterates through $s2
just like you'd need to. And, as a result, if you run $s2[9]
, then $s2
will no longer be lazy (i.e., $s2.is-lazy
will return False
).
And this is, in effect, what you did in the code in your question:
my $s = lazy gather for ^10 { take $_ };
say $s.is-lazy; # OUTPUT: «True»
say (for $s<> { $_ }).tail; # OUTPUT: «9»
say $s.is-lazy; # OUTPUT: «False»
Because Raku cannot ever know that it has reached the tail
of a lazy Seq, the only way it could tell you the .tail
is to fully iterate $s
. And that necessarily means that $s
is no longer lazy.
Two complications
It's worth mentioning two adjacent topics that aren't actually related but that are close enough that they trip some people up.
First, nothing I've said about lazy iterables not knowing their length precludes some non-lazy iterables from knowing their length. Indeed, a decent number of Raku types do both the Iterator role and the PredictiveIterator role – and the main point of a PredictiveIterator
is that it does know how many elements it can produce without needing to produce/iterate them. But PredictiveIterators
cannot be lazy.
The second potentially confusing topic is closely related to the first: while no PredictiveIterator
can be lazy (that is, none will ever have an .is-lazy
method that returns True
), some PredictiveIterator
s have behavior that is very similar to laziness – and, in fact, may even be colloquially referred to as "lazy".
I can't do a great job explaining this distinction because, quite honestly, I don't fully understand it myself. But I can give you an example: the .lines method on an IO::Handle
. It's certainly the case that reading the lines of a huge file behaves a lot like it's dealing with a lazy iterable. most obviously, you can process each line without ever having the whole file in memory. And the docs even say that "lines are read lazily" with the .lines
method.
On the other hand:
my $l = 'some-file-with-100_000-lines.txt'.IO.lines;
say $l.is-lazy; # OUTPUT: «False»
say $l.iterator ~~ PredictiveIterator; # OUTPUT: «True»
say $l.elems; # OUTPUT: «100000»
So I'm not quite sure whether it's fair to say that $l
"is a lazy iterable", but if it is, it's "lazy" in a different way than $s
was.
I realize that was a lot, but I hope it is helpful. If you have a more specific use case in mind for laziness (I bet it wasn't gathering the numbers from zero to nine!), I'd be happy to address that more specifically. And if anyone else can fill in some of the details with .lines
and other lazy-not-lazy PredictiveIterator
s, I'd really appreciate it!
lazy
:my $s = gather for ^10 { take $_; }; say $s.tail
– Unsatisfactorylazy
keyword? – Pneumonicgather
is asked if it's lazy, it returnsFalse
, whereas if alazy
is asked if it's lazy, it will returnTrue
. See my answer to About Laziness. – Carmelocarmen