New Moose Features in 0.34

Stevan on 2008-01-22T04:03:56

The latest release of Moose (0.34) brings with it some very nice features as well as a major re-factoring of the role system (I am finally happy with the design now too) and a re-factoring of the parameterized types. While most of the information on the new features is in the Changes file, I figured that they are cool enough to be blog-worthy too, so here goes.

New Maybe[`a] parameterized type

The handling of parameterized types has been re-factored so that it is now possible to add new parameterized types without needing them to be based off the ArrayRef or HashRef type (no syntactic sugar yet though). As a demonstration of this, and because it's just a useful thing anyway I have added the Maybe[`a] type. This type basically means "maybe there will be a value, and if there is, it will be of type `a", users of Haskell will be familiar with this (as will O'Caml users, it is similar to the 'option' type). Here is an example of the usage:

package Person; use Moose;

has 'first_name' => (is => 'rw', isa => 'Str'); has 'last_name' => (is => 'rw', isa => 'Str'); has 'middle_initial' => (is => 'rw', isa => 'Maybe[Str]');

The 'middle_initial' attribute now allows either a string or an undefined value to be stored.

Role method exclusion and aliasing

Role composition follows a strictly defined set of rules, this is one of the benefits of roles over your basic mix-ins or multiple inheritance. But sometimes, you want to subvert those rules and have more control over the composition process. The original Traits paper (on which Roles are based) included both 'exclusion' and 'aliasing' operations as a way of modifying the composition process. I included these features when I originally wrote Class::Trait, but since @larry had not added them into Perl 6 I hadn't included support in Moose. Now after several months of using Roles I now see the original wisdom of the Traits paper authors and these features are now available in Moose. Here are some examples of usage:

Lets create an Equality role, which provides the mechanism for determining, well, equality. package Equality; use Moose::Role;

requires 'equal'; sub not_equal { not((shift)->equal(@_)) }
Now lets define two more roles which do the Equality role, first a 'HasColor' role: package HasColor; use Moose::Role;

with 'Equality';

# create an attribute to hold RGB values has 'rgb' => (is => 'rw', isa => 'ArrayRef[Int]');

sub equal { ... code to compare RBG values ... }
And now a 'HasCoordinate' role package HasCoordinate; use Moose::Role;

with 'Equality';

# create an attribute to hold x/y values has 'coord' => (is => 'rw', isa => 'ArrayRef[Int]');

sub equal { ... code to compare x/y values ... }
Now if my 'Pixel' class wanted to use both the 'HasColor' and 'HasCoordinate' roles, ... package Pixel; use Moose;

with 'HasColor', 'HasCoordinate';
I would have a problem because their 'equal' methods would conflict. The conflict means that the 'Pixel' class must then implement an 'equal' method of it's own. I could implement it like so: sub equal { my ($self, $other) = @_; $self->HasColor::equal($other) && $self->HasCoordinate::equal($other); } but the hardcoding of role names like this is a design smell IMO. We also have lost color and coordinate comparison abilities in our 'Pixel' class. Here is a different approach using exclusion and aliasing to provide (IMO) a much superior solution.

package Pixel; use Moose;

with 'HasColor' => { excludes => 'equal', alias => { equal => 'color_equal' } }, 'HasCoordinate' => { excludes => 'equal', alias => { equal => 'coord_equal' } };

sub equal { my ($self, $other) = @_; $self->color_equal($other) && $self->coord_equal($other); }

The above code now removes the conflict between the 'equal' methods, but aliases them so that the features are not lost and finally implements equals in terms of them.

Of course this example is a little on the contrived side, but you get the point. Anyone who has messed around with roles enough will surely rejoice at this new feature addition.

Attribute Metaclass traits

We already have support for custom attribute metaclasses and this feature is used in a number of modules, MooseX::Getopt, MooseX::Storage and MooseX::AttributeHelpers to name a few. It allows you to extend the features of Moose attributes to provide additional functionality within the same Moose framework. The one problem with this feature has always been that it is very difficult to combine these extensions and use more than one at a time. Here is a (very contrived) example of how this feature will be useful.

package My::App; use Moose; use MooseX::Storage; use MooseX::Getopt;

has 'some_thing' => ( traits => [qw/DoNotSerialize NoGetopt/], is => 'rw', isa => 'ArrayRef[Big::Heavy::Things]', # ... );

Currently MooseX::Storage provides a DoNotSerialize metaclass which lets the MooseX::Storage engine know to skip that attribute when serializing. This is fine, until you need to also use the MooseX::Getopt provided NoGetopt metaclass (which, yes you guessed it, tells MooseX::Getopt that this is not a command line parameter). I will soon be releasing versions of those two modules which will make the above example work, allowing you to easily combine both non-serialization with non-getopt-parsing. (Sorry, that was the best example I could come up with right now, we are working on a possible cookbook entry so if this interests you keep you eyes out).

And lastly ...

And lastly, in addition to all this new functionality, I would like to announce the upcoming release of MooseX::Compile 0.01 (possibly by tomorrow morning). MooseX::Compile is a project here at $work which takes all the stored meta-information of a Moose class and compiles a .pmc file which defers loading the bulk of the Moose infrastructure and caches the already computed metaclasses. It is still in it's very early stages, and only supports a subset of Moose, but results are extremely promising.

- Stevan