Roles for Moose

Stevan on 2006-05-13T03:12:23

I have just released Moose 0.09_01 (as well as Class::MOP 0.29_01 to go with it). It is a fairly significant update with a number of new features, the most interesting of them being a fairly complete implementation of Roles. For the most part the Role features are based on Perl 6 roles, but I have added a few features from the original Traits paper and some new ideas from the Fortress spec.

Roles in Moose look a lot like classes in Moose do, in fact almost all Moose class keywords are supported in Moose::Role. Here are some basic examples.

package Equality;
use Moose::Role;

requires 'equal_to';

sub not_equal_to {
    my ($self, $other) = @_;
    not $self->equal_to($other);
}

This is a basic Equality role, it requires any class which 'does' it to implement a method called 'equal_to', and then provides a 'not_equal_to' method to compliment it. The 'requires' feature is not from Perl 6, but from the original Traits paper, although Perl 6 mimics this with the 'method stub' feature.

package Molecule::Organic;
use Moose::Role;

excludes 'Molecule::Inorganic';

package Molecule::Inorganic;
use Moose::Role;

This is a feature from the latest Fortress language spec (the example is taken from there as well). Obviously a class would not want to 'do' both Molecule::Organic and Molecule::Inorganic roles, they are mutually exclusive. The 'excludes' keyword allows you to state that explicitly. This will assure than any class which 'does' Molecule::Organic (and all it's subsequent subclasses) will never be allowed to 'do' Molecule::Inorganic, and vice versa.

package CGI::Application::Role::RequestLogger;
use Moose::Role

before 'run' => sub {
    my $self = shift;
    warn '-' x 80;
    warn "REQUEST_URI: " . $ENV{REQUEST_URI};
    warn "Query Params:";
    warn "\t$_ => " . $self->query->param($_) 
        for $self->query->param();
    warn '-' x 80;
};

Moose Roles also support the method modifiers as well. When a CGI::Application subclass 'does' this role, the 'run' method (which does all the dispatching) will be wrapped and a number of useful bits will be dumped to STDERR. Using it as simple as this:

package My::CGI::Application;
use Moose;

extends 'CGI::Application';
   with 'CGI::Application::Role::RequestLogger';

# ... now write your CGI::App subclass ...

Role now also have fairly sophisticated conflict detection mechanism as well. This allows roles to be combined together in a symmetrical way which will catch a number of compositional problems, and assure all requirements are met. We even handle mutually recursive roles like these:

package Role::Foo;
use Moose::Role;

requires 'foo';
sub bar { 'Role::Foo::bar' }

package Role::Bar;
use Moose::Role;

requires 'bar';
sub foo { 'Role::Bar::foo' }

package Foo::Bar;
use Moose;

# this will Just Work
with 'Role::Foo', 'Role::Bar';

This is similar to how Perl 6 will work, but once again, we diverge slightly. Moose roles can be combined (as above), and conflicts and requirements will be handled automatically, and they can also be combined manually with multiple 'with' keywords. This allows for a greater degree of control when you need it.

Roles are also showing a lot of promise at $work as an excellent plugin mechanism. This is where the 'requires' and 'excludes' features are coming in quite handy. They have the potential to prevent a whole class of composition issues which the usual 'mixin' based plugin methods are sometimes plagued with.

Some people have been experimenting with Roles to implement abstract classes and Java-style interfaces. The 'abstract' methods which a classes must implement can just be marked with the 'requires' keyword, and the class which 'does' the role has to implement them.

So, please give Moose 0.09_01 a try and play with Roles, and let me know what you think :)

- Stevan


Interesting assumption there...

Alias on 2006-05-13T03:43:29

"Obviously a class would not want to 'do' both Molecule::Organic and Molecule::Inorganic roles, they are mutually exclusive."

I should point out there are organic macromolecules that have the properties of inorganic ones.

So there are molecules out there that ->isa('Organic') but that by some clever tricks can ->does('Inorganic').

excludes seems like a dangerous keyword...

Re:Interesting assumption there...

Stevan on 2006-05-13T05:09:23

Having pretty much failed high school chemisty myself, I will take your word on that. And to be honest, it is not my example, I took it direct from the most recent Fortress spec.

"excludes seems like a dangerous keyword..."

It is.

Probably the best use case for 'excludes' is for tightly coupled role heirarchies, in which you want to provide two mutually exclusive interfaces (immutable and mutable come to mind). While forcing this exclusion might not be 100% appropriate in all cases, it may be appropriate for your roles (the key word here being your and applying only to the actual code written and not the theory behind the concept you are modeling). Having the ability to enforce this can go a long way to ensuring and enforcing proper usage.

And of course, as with many of Moose's features, you don't have to use it if you don't want to.

-Stevan

Re:Interesting assumption there...

Stevan on 2006-05-13T14:58:24

Whoops. Wrong link to the Fortress spec. This one is correct.

- Stevan