Pushing the limits

Elizabeth Mattijsen - Nov 13 '22 - - Dev Community

This is the second part of the "A gaze of iterators!" series.

Pushing the limits

So let's again look at the list of methods the iterator on 42 has:

.say for 42.iterator.^methods'
# new
# pull-one
# push-exactly
# push-all
# skip-one
# skip-at-least
# count-only
# sink-all
# bool-only
# push-until-lazy
# push-at-least
# skip-at-least-pull-one
# is-lazy
# is-deterministic
# BUILDALL
Enter fullscreen mode Exit fullscreen mode

Hmmm... .push-all looks interesting. Could it really be that simple? Let's see!

my @array;
42.iterator.push-all(@array);
say @array;  # [42]
Enter fullscreen mode Exit fullscreen mode

And how about a list of values?

my @array;
<a b c>.iterator.push-all(@array);
say @array;  # [a b c]
Enter fullscreen mode Exit fullscreen mode

Looking at this, you could realize that the above is just a convoluted way to write:

my @array = <a b c>;
say @array;  # [a b c]
Enter fullscreen mode Exit fullscreen mode

And you'd be right again! And now you have a better idea of what goes on under the hood. Well, at least conceptually, because the actual implementation is of course at liberty to take short-cuts to improve efficiency.

Don't like that one

The next method on that list is .skip-one. Looks like it's mostly like .pull-one, so let's check:

my $iter = <a b c>.iterator;
say $iter.skip-one; # 1
say $iter.pull-one; # b
say $iter.pull-one; # c
say $iter.skip-one; # 0
Enter fullscreen mode Exit fullscreen mode

So it looks like .skip-one really skips a value. But it also returns something? Indeed, it returns either 1 to indicate a successful skip (in this case "a" got skipped", and 0 for an unsuccessful skip (in this case because there is no value after "c").

Why not True and False you might ask? Well, these are all methods that work under the hood as efficiently as possible, and turning a native integer 1 into a boolean True would just be extra and unnecessary work.

What is it

Those two .is-lazy and .is-deterministic methods also look interesting:

say 42.iterator.is-lazy;           # False
say 42.iterator.is-deterministic;  # True
Enter fullscreen mode Exit fullscreen mode

The .is-lazy method indicates whether the iterator is lazy or not.

In hindsight, the term "lazy" was probably a bad choice. The most obvious thing about "lazy" iterators, is that you cannot calculate the number of elements it will produce. So the term "countable" (while reversing the meaning of the returned value) would probably have been better.

say (1..*).iterator.is-lazy;  # True
Enter fullscreen mode Exit fullscreen mode

is an example of a "lazy" iterator, of which you can not count the number of elements in a range of integers from 1 to infinity. If you try to do that with the .elems method, you will get an error:

say (1..*).elems;  # Cannot .elems a lazy list
Enter fullscreen mode Exit fullscreen mode

If it wouldn't produce the error, it would hang because it would be producing values "ad infinitum" literally! Before being able to tell you the number of elements.

The .is-deterministic method indicates whether the iterator, given a certain source, will always produce the same values in the same order. The Raku internals can optimize certain situations if it knows whether the produced values will always be the same.

say (1..10).iterator.is-deterministic;          # True
say (1..10).pick(*).iterator.is-deterministic;  # False
Enter fullscreen mode Exit fullscreen mode

Note the .pick method will produce the given values in a random order, so clearly not deterministic!

say (1..10).pick(*);  # (7 1 9 2 4 5 10 8 3 6)
say (1..10).pick(*);  # (4 6 8 9 2 10 7 5 1 3)
Enter fullscreen mode Exit fullscreen mode

That's weird

What does .BUILDALL do? Actually, nothing that should concern you. The ALLCAPS of the method really indicates that there is something special going on!

The .BUILDALL method is a method that is automatically generated for every class, and it contains the default logic to initialize an object of that class. There is no source code for it: the definition of a class determines how that method will be directly generated into executable bytecode.

What are you sinking about?

Now, the other methods all have names that make sense, probably. But there is one method that appears to be different: .sink-all. Let's see what happens if we call that:

say <a b c>.iterator.sink-all;  # IterationEnd
Enter fullscreen mode Exit fullscreen mode

That's informational? Not! But what did it do? In this particular case, it will mark the iterator as completed. Not very useful.

But there are other (very common) cases where calling the .sink-all method is very useful. Remember that a for loop is really just a .map of which the results are discarded?

my $seen = 0;
(1..10).map({++$seen}).iterator.sink-all;
say $seen;  # 10
Enter fullscreen mode Exit fullscreen mode

The .sink-all method is used internally for those iterators that are just executed for their side-effects. So the above is just a very complicated way to write:

my $seen = 0;
++$seen for 1..10;
say $seen;  # 10
Enter fullscreen mode Exit fullscreen mode

The term "sink" is the Raku equivalent for what other programming languages call "void context". But more about that later in this series.

Conclusion

This concludes the second part of the series, in which most of the other methods that you can call on an iterator have been explained. Specifically the .skip-one, .push-all, .is-lazy, .is-deterministic and .sink-all methods. With a side-order of .pick.

Questions and comments are always welcome. You can also drop into the #raku-beginner channel on Libera.chat, or on Discord if you'd like to have more immediate feedback.

I hope you liked it! Thank you for reading all the way to the end.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player