Author Archive

Day 18: Roles

December 18, 2009

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!

Day 11: Classes, attributes, methods and more

December 11, 2009

We excitedly tear the shiny wrapping paper off today’s gift, and inside we find something that nobody could object to! It’s the Perl 6 object model, in all its class-declaring, role-composing, meta-modelling glory. But before we get too carried away with the high-powered stuff, let’s see just how easy it is to write a class in Perl 6.

class Dog {
    has $.name;
    method bark($times) {
        say "w00f! " x $times;
    }
}

We start off by using the class keyword. If you’re coming from a Perl 5 background, you can think of class as being a bit like a variant of package that gives you a bunch of classy semantics out of the box.

Next, we use the has keyword to declare an attribute along with an accessor method. The . that you see in the name is a twigil. Twigils tell you something special about the scoping of a variable. The . twigil means “attribute + accessor”. Other options are:

has $!name;       # Private; only visible in the class
has $.name is rw; # Generates an l-value accessor

Next comes a method, introduced using the method keyword. method is like sub, but adds an entry to the methods table of the class. It also automatically takes the invocant for you, so you don’t have to write it in the parameter list. It is available through self.

All classes inherit a default constructor, named new, which maps named parameters to attributes. We can call this on Dog – the type object of the Dog class – to get a new instance.

my $fido = Dog.new(name => 'Fido');
say $fido.name;  # Fido
$fido.bark(3);   # w00f! w00f! w00f!

Notice that the method call operator in Perl 6 is . rather than Perl 5’s ->. It’s 50% shorter, and will be familiar to developers coming from a range of other languages.

Of course, there’s inheritance, so we can introduce a yappy puppy.

class Puppy is Dog {
    method bark($times) {
        say "yap! " x $times;
    }
}

There’s also support for delegation.

class DogWalker {
    has $.name;
    has Dog $.dog handles (dog_name => 'name');
}
my $bob = DogWalker.new(name => 'Bob', dog => $fido);
say $bob.name;      # Bob
say $bob.dog_name;  # Fido

Here, we declare that we’d like calls to the method dog_name on the class DogWalker to forward to the name method of the contained Dog. Renaming is just one option that is available; the delegation syntax offers many other alternatives.

The beauty is more than skin deep, however. Beneath all of the neat syntax is a meta-model. Classes, attributes and methods are all first class in Perl 6, and are represented by meta-objects. We can use these to introspect objects at runtime.

for Dog.^methods(:local) -> $meth {
    say "Dog has a method " ~ $meth.name;
}

The .^ operator is a variant on the . operator, but instead makes a call on the metaclass – the object that represents the class. Here, we ask it to give us a list of the methods defined within that class (we use :local to exclude those inherited from parent classes). This doesn’t just give us a list of names, but instead a list of Method objects. We could actually invoke the method using this object, but in this case we’ve just ask for its name.

Those of you into meta-programming and looking forward to extending the Perl 6 object model will be happy to know that there’s also a declarational aspect to all of this, so uses of the method keyword actually compile down to calls to add_method on the meta-class. Perl 6 not only offers you a powerful object model out of the box, but also provides opportunities for it to grow to meet other future needs that we didn’t think of yet.

These are just a handful of the great things that the Perl 6 object model has to offer; maybe we’ll discover more of them in some of the other gifts under the tree. :-)


Follow

Get every new post delivered to your Inbox.

Join 37 other followers