DEV Community

Elizabeth Mattijsen
Elizabeth Mattijsen

Posted on • Edited on

Role playing

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

By any other name

Let's look at the names of the iterators in part 2. For this, I'm going to use the .^name method. As we've seen before, .^foo means "calling the .foo method on the object's meta-object".

say <a b c>.iterator.^name;
# Rakudo::Iterator::ReifiedListIterator
say (1..*).iterator.^name;
# Rakudo::Iterator::IntRangeUnending
say (1..10).pick(*).iterator.^name;
# List::PickN
say (1..10).map({++$_}).iterator.^name;
# Any::IterateOneWithoutPhasers
Enter fullscreen mode Exit fullscreen mode

As you can see, the names of the classes of these iterator objects are all over the place. But they all provide the same interface: being able to call methods such as .pull-one, .push-all and .sink-all.

In many programming languages, you'd expect all of these classes to be sharing the same parent class. In the Raku Programming Language you can indeed inherit from a parent class. You can check the lineage of a class with the .^mro method (for method resolution order).

say <a b c>.iterator.^mro;
# ((ReifiedListIterator) (Any) (Mu))
say (1..*).iterator.^mro;
# ((IntRangeUnending) (Any) (Mu))
say (1..10).pick(*).iterator.^mro;
# ((PickN) (Any) (Mu))
say (1..10).map({++$_}).iterator.^mro;
# ((IterateOneWithoutPhasers) (Any) (Mu))
Enter fullscreen mode Exit fullscreen mode

That is odd? They all seem to inherit from Any and Mu? Yet, one can not call the .pull-one method on every object that just inherits from Any and Mu:

say 42.^mro;
# ((Int) (Cool) (Any) (Mu))
say 42.pull-one;
# No such method 'pull-one' for invocant of type 'Int'
Enter fullscreen mode Exit fullscreen mode

Role playing

The Raku Programming Language also provides a thing called "roles". In short, you could think of a role as a collection of methods that will be "implanted" into a class if the class itself does not provide a method implementation for it.

All of these iterator classes that we've seen here, actually do the Iterator role. And just as with the ^.mro, you can introspect which roles a class performs by calling the .^roles method. Let's see how that works out here:

say <a b c>.iterator.^roles;
# ((PredictiveIterator) (Iterator))
say (1..*).iterator.^roles;
# ((Iterator))
say (1..10).pick(*).iterator.^roles;
# ((Iterator))
say (1..10).map({++$_}).iterator.^roles;
# ((SlippyIterator) (Iterator))
Enter fullscreen mode Exit fullscreen mode

So it looks like some classes are actually playing more than one role. But they all also do the Iterator role, it looks like.

How to be an iterator

To make a class be an iterator, one must tell the class to do the Iterator role. That's pretty simple, no? Let's start with an empty class that just wants to be an iterator. You do that by using does:

class Foo does Iterator {
}
===SORRY!=== Error while compiling -e
Method 'pull-one' must be implemented by Foo
because it is required by roles: Iterator.
Enter fullscreen mode Exit fullscreen mode

So we need to actually provide some type of implementation for the interface that the Iterator role is providing. Ok, so let's make a very simple method pull-one that will randomly return True or False:

class TrueFalse does Iterator {
    method pull-one() { Bool.roll }
}
say TrueFalse.pull-one;  # True | False
Enter fullscreen mode Exit fullscreen mode

The .roll method randomly picks a single value from a set of values. When called on an enum, it will randomly select one of the enums values. And the Bool enum has True and False as its values.

Of course, this is all very boring, let's make it more interesting:

class YeahButNoBut does Iterator {
    method pull-one() {
        Bool.roll ?? "Yeah but" !! "No but"
    }
}
say YeahButNoBut.pull-one;  # Yeah but | No but
Enter fullscreen mode Exit fullscreen mode

So we now have a class that produces an iterator. But how would you actually use that in any "normal" way in your program? Well, by embedding the iterator into another class, and have a method .iterator in it that returns the iterator class:

class Jabbering {
    method iterator() {
        my class YeahButNoBut does Iterator {
            method pull-one() {
                Bool.roll ?? "Yeah but" !! "No but"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Note here that the .iterator method actually returns the class object itself (usually referred to as the "type object"). Why? Because that's all we need from this iterator class: in its current form, this class doesn't need to keep any state.

Also note that classes in the Raku Programming Language can be lexically scoped by prefixing them with my, just as you would lexically scoped variables. This makes sense in this case, as there would be no need for the iterator class outside of the scope of the "Jabbering" class.

So how would this look with by .^name, ^.mro and .^roles, as we've shown with all of the other iterators?

say Jabbering.iterator.^name;
# Jabbering::YeahButNoBut
say Jabbering.iterator.^mro;
# ((YeahButNoBut) (Any) (Mu))
say Jabbering.iterator.^roles;
# ((Iterator))
Enter fullscreen mode Exit fullscreen mode

As you can see, the Jabbering class iterator has the expected name. And the Jabbering class inherits from Any and Mu, and performs the Iterator role.

So with all of this out of the way, now you can start jabbering!

.say for Jabbering;
Enter fullscreen mode Exit fullscreen mode

Hmmm... that doesn't stop now, does it?

Indeed it doesn't. As to why, that's for the next instalment in this series!

Conclusion

This concludes the third part of the series, in which the concept of roles in the Raku Programming Language is introduced, along with does. And that you can alter the scope of a class by prefixing it with my.

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.

Top comments (2)

Collapse
 
2colours profile image
2colours

Hello,

I feel I have a little bit of a problem here.

Firstly, I don't feel I understand much of this metamodel business. All I really understand is that there is some introspection going on.

Also, when I ran the class definition and the following introspection calls, I didn't get what the comments said:

Jabbering::YeahButNoBut
((Jabbering) (Any) (Mu))
()
Enter fullscreen mode Exit fullscreen mode

Could it be that the .iterator call was missing from the code snippet?

Collapse
 
lizmat profile image
Elizabeth Mattijsen

Indeed, it was! Good catch! Fixed