DEV Community

loading...

Assuming roles

jj profile image Juan Julián Merelo Guervós ・4 min read

Mariano does Chavo del Ocho, graffiti in Granada
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.

Discussion

pic
Editor guide