Day 18: Roles

As the snow falls outside, we grab a glass of mulled wine – or maybe a cup of eggnog – to enjoy as we explore today’s exciting gift – roles!

Traditionally in object oriented programming, classes have taken on two tasks: instance management and re-use. Unfortunately, this can end up pulling classes in two directions: re-use wants them to be small and minimal, but if they’re representing a complex entity then they need to support all of the bits it needs. In Perl 6, classes retain the task of instance management. Re-use falls to roles.

So what does a role look like? Imagine that we are building up a bunch of classes that represent different types of product. Some of them will have various bits of data and functionality in common. For example, we may have a BatteryPower role.

role BatteryPower {
    has $.battery-type;
    has $.batteries-included;
    method find-power-accessories() {
        return ProductSearch::find($.battery-type);
    }
}

At first glance, this looks a lot like a class: it has attributes and methods. However, we can not use a role on its own. Instead, we must compose it into a class, using the does keyword.

class ElectricCar does BatteryPower {
    has $.manufacturer;
    has $.model;
}

Composition takes the attributes and methods – including generated accessors – from the role and copies them into the class. From that point on, it is as if the attributes and methods had been declared in the class itself. Unlike with inheritance, where the parents are looked at during method dispatch, with roles there is no runtime link beyond the class knowing to say “yes” if asked if it does a particular role.

Where things get really interesting is when we start to compose multiple roles into the class. Suppose that we have another role, SocketPower.

role SocketPower {
    has $.adapter-type;
    has $.min-voltage;
    has $.max-voltage; 
    method find-power-accessories() {
        return ProductSearch::find($.adapter-type);
    }
}

Our laptop computer can be plugged in to the socket or battery powered, so we decide to compose in both roles.

class Laptop does BatteryPower does SocketPower {
}

We try to run this and…BOOM! Compile time fail! Unlike with inheritance and mix-ins, role composition puts all of the roles on a level playing field. If both provide a method of the same name – in this case, find-power-accessories – then the conflict will be detected as the class is being formed and you will be asked to resolve it. This can be done by supplying a method in our class that says what should be done.

class Laptop does BatteryPower does SocketPower {
    method find-power-accessories() {
        my $ss = $.adapter-type ~ ' OR ' ~ $.battery-type;
        return ProductSearch::find($ss);
    }
}

This is perhaps the most typical use of roles, but not the only one. Roles can also be taken and mixed in to an object (on a per-object basis, not a per-class basis) using the does and but operators, and if filled only with stub methods will act like interfaces in Java and C#. I won’t talk any more about those in this post, though: instead, I want to show you how roles are also Perl 6’s way of achieving generic programming, or parametric polymorphism.

Roles can also take parameters, which may be types or just values. For example, we may have a role that we apply to products that need to having a delivery cost calculated. However, we want to be able to provide alternative shipping calculation models, so we take a class that can handle the delivery calculation as a parameter to the role.

role DeliveryCalculation[::Calculator] {
    has $.mass;
    has $.dimensions;
    method calculate($destination) {
        my $calc = Calculator.new(
            :$!mass,
            :$!dimensions
        );
        return $calc.delivery-to($destination);
    }
}

Here, the ::Calculator in the square brackets after the role name indicates that we want to capture a type object and associate it with the name Calculator within the body of the role. We can then use that type object to call .new on it. Supposing we had written classes that did shipping calculations, such as ByDimension and ByMass, we could then write:

class Furniture does DeliveryCalculation[ByDimension] {
}
class HeavyWater does DeliveryCalculation[ByMass] {
}

In fact, when you declare a role with parameters, what goes in the square brackets is just a signature, and when you use a role what goes in the square brackets is just an argument list. Therefore you have the full power of Perl 6 signatures at your disposal. On top of that, roles are “multi” by default, so you can declare multiple roles with the same short name, but taking different types or numbers of parameters.

As well as being able to parametrize roles using the square bracket syntax, it is also possible to use the of keyword if each role takes just one parameter. Therefore, with these declarations:

role Cup[::Contents] { }
role Glass[::Contents] { }
class EggNog { }
class MulledWine { }

We may now write the following:

my Cup of EggNog $mug = get_eggnog();
my Glass of MulledWine $glass = get_wine();

You can even stack these up.

role Tray[::ItemType] { }
my Tray of Glass of MulledWine $valuable;

The last of these is just a more readable way of saying Tray[Glass[MulledWine]]. Cheers!

13 thoughts on “Day 18: Roles

    1. The specification talks about a Rational role to store fractional numbers, and that’s parameterized by the type of the numerator and denominator:

      role Rational[::NumT, ::DenomT] {
         has NumT $.numerator;
         has DenomT $.denominator;
         ...
      }
      
      class Rat does Rational[Int, int64] { }
      class FatRat does Rational[Int, Int] { }
      
    1. One superficial reason, which borders on begging the question, is that ‘does’ has a much tighter precedence than the comma. ‘does’ falls under “structural infix”, level 12, in the precedence table, whereas the comma is on level 19.

      1. That’s not quite right – the does here is not parsed as an operator, but rather as a trait modifier. It’s defined as:

            token trait_mod:does {
                :my $*PKGDECL ::= 'role';
                <sym>:s <module_name>
            }

        So at the moment you need to write “does” each time, but I guess it’d be possible to tweak the trait_mod parsing rule there to allow multiple. One issue is if people will view them as somehow “grouped”, when in reality it means nothing extra at all over writing “does”… The harder problem is that the trait mod compiles to a multi-dispatch, so we’d have to work out what happens there (I guess we’d dispatch a list of things, and the candidate that handles a list would iterate and re-dispatch on each of them). Anyway, it’s a little deeper than just a syntax change.

  1. But honestly, how is it better then abstract classes with arguments (or templates as they are called in C++)? It appears to me as if someone just invented a new name for something that is already there in other languages.

    1. The option to have them parameterized is really just a nice extra, and not the heart of what roles are about. The key thing in roles is the composition mechanism, which has different semantics from inheritance and thus doesn’t silently make choices in the case of conflicts, which leads to fragile hierarchies. In academia the concept has been referred to as traits; Perl 6 chose the name “roles” as it better indicates their use case. The initial academic work on traits, iirc, was done this decade, and while various languages are exploring them, the composition mechanism is an innovation.

      You’re right that the parameterization isn’t ground-breaking in itself. One difference is that we use multiple dispatch to decide between possible variants of a role based upon the arguments. Another thing that distinguishes Perl 6 from some (but not all) implementations of things like this is that arguments may be values or types. The Perl 6 standard grammar makes use of roles parameterized on values.

      Hope this helps,

      Jonathan

      1. From what I understood the difference between composition and inheritance is forcing user to manually resolve name conflicts. If so then personally I’d prefer to have this behaviour work for inheritance and remove the whole “rule” things which IMHO serves no purpose (yes, I’m of an opinion that the less entities the better). Or introduce two words for inheriting from base classes — one with inheritance and another with composition behaviour.

        Also “we can not use a role on its own” but if it’s a parametrized role we can use it on its own if we specify parameters (as seen in the last example). Doesn’t this inconsequence strike you?

  2. @mina86: Implementing the Perl 6 rule behaviour into normal class inheritance would mean naming two different things equally. Didn’t we have enough of this in Perl 5? I am happy that different concepts get different names in Perl 6.

    1. Honestly I don’t see it. For me it seems classes and rules are the very same thing expect inheritance works a bit different plus rule can be parametrized and (by arbitrary decision) class cannot.

      1. “Different things should look different” is a Perl 6 design principle. Thus why we have a method routine declarator (even though a method is just a sub that takes an invocant and can be dispatched to a bit differently), a grammar package declarator (even though a grammar is just a class that inherits from Grammar by default) and so forth.

        As a further benefit, the different names imply something about the purpose. Sure, I can write a class with named regexes in it (albeit missing the useful built-ins that I’d inherit if I’d used the grammar keyword) and a grammar with methods in it. But that doesn’t mean having them called different things to state the difference in intent is a bad thing.

        To get a better grasp of the differences between inheritance and composition, I suggest reading the original paper, which offers a much more detailed explanation:

        Click to access Scha02bTraits.pdf

        Finally, you noted that I did use a role on its own in my last example. I guess you meant:

        my Tray of Glass of MulledWine $valuable;

        But here I am not using the role – just setting it as a type constraint on the variable. Any object that does that role could then be stored in $valuable.

      2. Now I see it wasn’t used by only specified type constraint (you can get a bit of a headache if you switch too fast between Perl and C++ ;) ). Thanks for pointing that out — it actually makes more sense to me now. :P

        Still, two more questions if I may:

        First of all, what happens if two roles use the same attribute? The paper pointed out traits must not have attribute to avoid diamond problem (for those who don’t know what it is and have better things to do then read overly-long papers ;) it’s situation where class A inherits from B and C and both B and C inherit from D) and here it seems as if the problem was not avoided at all.

        And finally, from what I’ve understood one cannot parametrize classes. Question is: Why? For instance, a legitimate use case would be to have a “Container[::Type]” role and a “RBTree[::Type]” class which “does” the role.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.