Strangely Consistent

Musings about programming, Perl 6, and programming Perl 6

June 28 2011: Roles

Having learned how to create classes, we might go on to create them to our hearts' content:

class Hammer {
    method hammer($stuff) {
        say "You hammer on $stuff. BAM BAM BAM!";
    }
}

class Gavel {
    method hammer($stuff) {
        say "You hammer on $stuff. BAM BAM BAM!";
    }
}

class Mallet {
    method hammer($stuff) {
        say "You hammer on $stuff. BAM BAM BAM!";
    }
}

But... notice something here? These three classes contain the exact same method, repeated in each class. We have to do that if we want each of Hammer, Gavel and Mallet to have the ability to hammer. (And that's reasonable.) But it's a pity that we have to triplicate the method.

Why is it a pity? Well, it's more to write, for one thing. Also, the methods might not be this close to each other in a real-world example, and one day we may decide to change something in the hammer method, not realizing that it's in three different places... which generally leads to a bunch of pain and suffering.

So here we are, with this new toy, classes, and already they exhibit a problem. We'd somehow like to re-use the hammer method in each three classes.

A new concept, the role comes to our rescue:

role Hammering {
    method hammer($stuff) {
        say "You hammer on $stuff. BAM BAM BAM!";
    }
}

Whereas classes are often named after an appropriate noun, roles are often named after a participle, such as Hammering. It's not a hard-and-fast rule, but it's a good rule of thumb. I really shouldn't be talking about thumbs, what with all the hammering going on...

Now the class definitions become easy:

class Hammer does Hammering {}
class Gavel  does Hammering {}
class Mallet does Hammering {}

Ah, yes. We like that.

What's happening under the hood when we do does on a class? All the methods from the role definition are copied into the class definition. Since it's a copy operation, we can do it with as many classes as we want.

So, here's what we do: we put methods in roles when we figure we'll re-use them.

But that's not the only benefit we get. There's at least two more:

my $hammer = Hammer.new;    # create a new hammer object
say $hammer ~~ Hammer;      # "Bool::True" -- yes, this we know
say $hammer ~~ Hammering;   # "Bool::True" -- ooh!

So the $hammer knows that it does Hammering. Essentially, we now have a way to ask not only what class an object belongs to, but also what roles it incorporates. That's useful if we're unsure what kind of object we're dealing with:

if $unkown_object ~~ Hammering {
    $unknown_object.hammer("that nail over there");     # will always work
}

Can a class take on several roles at once? Yes, it can:

role Flying {
    method fly {
        say "Whooosh!";
    }
}

class FlyingHammer does Hammering does Flying {}

Having a class do several roles in that way introduces an interesting possibility: that of conflicts, when two methods with the same name from two different roles try to occupy the same class. What happens in such a case? Well, there are at least three possibilities:

  1. First role wins. Its method gets to live in the class.
  2. Last role wins. It overwrites the previous method.
  3. Compilation fails. The conflict has to be resolved.

And option 3 is the right answer in this case. The reason for this is the same as before: as classes and projects grow larger, the programmer might not realize that there's a conflict between two roles somewhere. So we flag it.

role Sleeping {
    method lie {
        say "Reclining horizontally...";
    }
}

role Lying {
    method lie {
        say "Telling an untruth...";
    }
}

class SleepingLiar does Sleeping does Lying {}    # CONFLICT!

Next question, then: when there's a role conflict in a class, how do we fix it? Simple: by defining a method with that name in the class itself:

class SleepingLiar does Sleeping does Lying {
    method lie {
        say "Lying in my sleep....";
    }
}

If you want to call a method from a particular role, there's a syntax for that:

class SleepingLiar does Sleeping does Lying {
    method lie {
        self.Sleeping::lie;
    }
}

And that's roles. They mix in reusable behavior into classes.

Now, we're finally ready to tackle that text adventure of ours!