Assuming roles
Juan Julián Merelo Guervós
Posted on December 21, 2017
I have seen many texts talking about object orientation use ontological metaphors. A Lamborghini is a car is a vehicle. Not bad, as metaphors go, but it pins you to a hierarchical structure where you refine until you obtain what you want and if you want to mix stuff, and have say a flying car, you have sometimes to graft two ontological trees together in a way that is not pleasant to any of them.
Fortunately, modern object oriented programming, while following that general structure, does allow for more modern, component based, classes. These components are generally called mixins, but also roles, modules, interfaces or traits. Want a flying car? Grab a Trabbi, duck-tape some kites, and here you go!
Many modern languages include these mixins.
Ruby, for instance, calls them modules. We might want, as we have done, to describe text using these classes
#!/usr/bin/env ruby
module Words
attr :words
def to_string( ligature )
return @words.join(ligature)
end
end
class Paragraph
include Words
def initialize(words)
@words = words
end
def to_string()
return "\n"+@words.join(" ")+"\n"
end
end
sentence = Paragraph.new("these are the words".split(/\s+/))
puts sentence.to_string()
Text things include words, so we define Words
to contain them. And we define it as module
to indicate this is not a complete thing, but only a component that, Lego-like, starts to make sense once it has been included in a full-fledged Class
, like the one below, which does include Words
at the beginning and then directly uses the component-defined variable, @words
to initialize it and to print stuff. Really not groundbreaking thing, which in this case, besides, could maybe be used via inheritance, but actually a Paragraph
is-not-a
Words
, so we're ontologically safe here and besides have created a simple component which can be tested and managed independently.
And Perl6 also includes mixins.
Only it calls them roles, but they are pretty much the same. Let's see how this thing works:
#!/usr/bin/env perl6
# Thanks to https://perl6advent.wordpress.com/2009/12/18/day-18-roles/
role Words {
has @.words;
method to-string( Str $ligature ) {
return @!words.join( $ligature );
}
}
class Paragraph does Words {
submethod BUILD( :@!words ) {
}
method to-string() {
return "\n" ~ self.Words::to-string( " " ) ~ "\n";
}
}
my $sentence = Paragraph.new(words => "these are the words".split(/\s+/));
say $sentence.to-string();
Except for the fact that we need explicit declaration of @.words
, in this case as a public variable, which can be automatically retrieved from objects using simply words
, it's pretty much the same as the Ruby one, I know, down to the @
, which Ruby took from Perl5, by the way. Perl6 uses the keyword has
to declare variables, as in this role has words. Perl6, the language you can read aloud. Unlike Ruby, the twigil used for declaration (@.
) becomes @!
when you actually use it inside the object. Why? Well, this generation is kind of self-focused to the point that they make an exclamation when dealing with themselves. Self → !
, with the @
in front to indicate that we're dealing with some array-like thing. We use also Str ligature
to declare the signature of the to-string
method; it will error if we use it otherwise, and as well it should, since non-stringy data would not make a lot of sense.
But roles are made to be played, and Paragraph
does Words
. However, this is a class and we have to assign values to the instance variables in the constructor, which is a submethod
in Perl6. Submethods cannot be inherited, you cannot really inherit the constructor which is called BUILD
in this language, right? Every object has got its own little way to bootstrap itself into existence; however, since this paragraph does words, just mentioning the variable we will intialize is enough to generate the assignment code. :@!words
is shorthand for we are going to initialize the @!words
variable using a hash (thus the :
) with the key words
. This is what we do later on:
my $sentence = Paragraph.new(words => "these are the words".split(/\s+/));
splits
does split using a regular expression
Which we saw briefly in this post of the same "Grammars are my friends" series.
giving an array of words, which is what @!words
actually wants. But we need to use the to-string
method, which is named exactly the same as the one in the Role
. No sweat:
return "\n" ~ self.Words::to-string( " " ) ~ "\n";
self.Words
will extract the Wordy part of the Paragraph and use that particular to-string
, not the one here, provoking an interminable loop that would gobble up the whole universe. ~
in this case concatenates
Also stringifies. We'll return to that role later on in the series.
We can define a different client for our Role:
class Heading does Words {
submethod BUILD( :@!words ) {
}
method to-string() {
return "\n<h1>" ~ self.Words::to-string( " " ) ~ "</h1>\n";
}
}
Which is pretty much the same, except it returns a level-1 HTML heading.
Lots of things we can do with this
But we were talking about Grammars. Grammars in Perl6 are actually classes.
So the more we know about them, the better. We'll get happily back to grammars very soon.
Posted on December 21, 2017
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.