A gaze of iterators!

Elizabeth Mattijsen - Nov 11 '22 - - Dev Community

This blog post provides an introduction to iterators in the Raku Programming Language.

It requires some basic understanding of Raku code. One could consider the Don't fear the grepper! series as a prerequisite for this series of blog posts.

Iterator Central

Iterators are at the basis of every type of iteration in the Raku Programming Language, except for loop (which uses a counter, or iterates indefinitely), while and until (which iterate while a condition is True / False).

Iterators are everywhere in Raku: all values and classes support having the iterator method called on them.

say 42.iterator;  # Rakudo::Iterator::ReifiedListIterator.new
Enter fullscreen mode Exit fullscreen mode

So why would that say what it does? Well, as we've seen before, the say subroutine will call the .gist method on whatever it got. And the default (inherited) .gist method on instances of classes shows the name of the class and how it could possibly be created.

Same for any other class, even one of your own:

class Foo { }
say Foo.new;  # Foo.new
Enter fullscreen mode Exit fullscreen mode

What can you use an iterator for?

Good question! Actually, in general you wouldn't be using an iterator yourself in your code. You would provide Raku with an iterator (usually implicitly), and let that do the work for you. In general.

But to get the feel of what an iterator can do, we're going to tinker with iterators a bit in this series of blog posts. Just to get a feel of what is going on under the hood, as it were.

Looking on the inside

First of all, one would like to know which methods you can call on an iterator object. Fortunately, the Raku Programming Language has many introspection capabilities.

One of them is the .methods method on the meta-object of the iterator. Don't think too much about that at this point: just know that there's a special syntax for calling a method on the meta-object of an object: .^method-name.

So let's see what the iterator on 42 can do:

.say for 42.iterator.^methods;
# new
# pull-one
# push-exactly
# push-all
...
Enter fullscreen mode Exit fullscreen mode

.new? There's nothing new about that? Well, yes and no. The fact that it is listed here, means that the class has its own (not inherited) method new. Whether that is useful information, is up to the reader!

The next one is .pull-one. Let's see what happens if we call that on the iterator object:

say 42.iterator.pull-one; # 42
Enter fullscreen mode Exit fullscreen mode

I guess you could hardly call that surprising. But what happens if you would call the .pull-one method for a second time?

my $iter = 42.iterator;
say $iter.pull-one; # 42
say $iter.pull-one; # IterationEnd
Enter fullscreen mode Exit fullscreen mode

Hmmmm... what's this IterationEnd you say? It's a very special sentinel value that indicates that the iterator is done producing values. And that you should not call any methods on the iterator anymore (as the results will be undefined).

Ok, so let's try this again, this time with a small list of values:

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

Or, shorter:

my $iter = <a b c>.iterator;
say $iter.pull-one for ^4;
# a
# b
# c
# IterationEnd
Enter fullscreen mode Exit fullscreen mode

Pulling until it's done

Now that we know that the final value is IterationEnd, we should be able to write a loop checking for that value, right? And end the loop on that? Indeed we can! But it requires some special care:

my $iter = <a b c>.iterator;
until ($_ := $iter.pull-one) =:= IterationEnd {
    .say;
}
# a
# b
# c
Enter fullscreen mode Exit fullscreen mode

That's maybe a lot of colons all of a sudden!

The first colon is in := which is the binding operator. It aliases the left side with the right side: in this case with the topic $_. It's to make sure that we're going to compare the actual value directly, rather than a value in a variable (as values in variables may actually appear differently to the outside world, if they want to).

The second one is the =:=, the identity operator. It checks whether both sides refer to the same item in memory. Whether they are really the same object.

Looking at this code, you might realize that this is actually a convoluted way to write:

for <a b c> {
    .say
}
Enter fullscreen mode Exit fullscreen mode

And you'd be right: what you see above is more or less essentially what is happening under the hood. Of course, reality is a bit more complicated: in this example we for instance didn't account for handling any loop phasers (FIRST, NEXT and LAST). But the basic principle is the same!

And because every value and every class can take a call to the iterator method, you should understand now why this works:

for 42 {
    .say
}
# 42
Enter fullscreen mode Exit fullscreen mode

Indeed, because you can call the .iterator method on any class or object!

Conclusion

This concludes the first part of the introduction to iterators, and possibly to the Raku Programming Language.

It introduced the iterator and ^methods methods, as well as the pull-one method and the special IterationEnd sentinel value for iterators. And it casually introduced the := binding operator and the =:= identity operator.

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