Alright, lets try this again :)
Last week I accidentally published an early version of this article, and unfortunately before I could pull it, my feed got picked up by some the the more common aggregation sites. Rather than have an broken link I decided to just refer people to the work in progress code. If you read that, this version just has some minor tweaks. Thanks!
TITLE
Parameterized Roles and Method Traits - Discussion on MooseX::Declare advanced code composition features
ARE YOU READY?
This is a discussion of some advanced features of Moose and MooseX::Declare. If you are new to Perl or Moose
I recommend the following tutorials and web resources to get you up to
speed. These should be your first stop if you are new to Moose or never heard of roles. This is far from an exhaustive list, but should get you started.
See the web site "Learning Perl", http://learn.perl.org
See the excellent Moose::Tutorial which is a good lead in to the general Moose documentation
- MooseX::Declare documentation
MooseX::Declare's documentation is not as complete as the general Moose
documentation (there's no tutorial, for example) but it does have a
good number of examples and many test cases worth reviewing.
The http://moose.perl.org has a good collection of links to blogs, articles and presentations.
INTRODUCTION
You might already know I'm a big fan of MooseX::Declare, which takes all the programming ease and power of Moose,
and adds a sweeter syntax which includes the most flexible method body
signature system I've seen in any popular programming language. For the
uninitiated you can go from:
package MooseClass;
use Moose;
sub my_method {
my ($self, $name, $age) = @_;
return "Hi $name, you are $age years old";
}
__PACKAGE__->meta->make_immutable;
To something like:
use MooseX::Declare;
class MooseXDeclareClass {
method my_method(Str $name, Int $age) {
return "Hi $name, you are $age years old";
}
}
This gives you a more modern class and method declaration syntax
which lets you dispense with all the boilerplate validation and mucking
around with "@_". You get Moose type constraint checking (Moose::Util::TypeConstraints)
in method signatures for free, as well as type contraint coercions
which grant a level of declaritve polymorphism in your method
declarations previously missing from Perl. Additionally this looks much
more like what a modern programmer expects to see when creating
classes, which should make it easier for people coming from other
languages to get excited about modern Perl. The only (current)
downsides are a performance penalty and sometimes if you have syntax
errors the error messages can be a little cryptic. Both issues are a
work in progress but if they really bother you you can always drop down
to 'classic' Moose and use Method::Signatures::Simple to get the shiny 'method' keyword (although you loose the method body signature type constraint checking).
Anyway, you may not know it, but MooseX::Declare has some sweet syntax for declaring parameterized Roles (via MooseX::Role::Parameterized) and Method Traits. Both features are relatively undocumented in MooseX::Declare, so here's the basic idea. Hopefully this article can lead (with your feedback) to improved core MooseX::Declare documentation.
PARAMETERIZED ROLES
Think of a Parameterized Role as a sort of Role generator. It lets
you make more generic roles by allowing you to specify role parameters
at the time you include the role. These parameters (which are similar
to standard Moose attributes) can be used to inform and influence how a role works before its applied to its consuming class. For example, MooseX::Role::BuildInstanceOf
makes it easier to aggregate functionality into a class by
automatically generating accessors for a target instance. However, the
syntax for MooseX::Role::Parameterized in classic Moose is a bit verbose. With MooseX::Declare you can replace:
package MyParameterizableRole;
use MooseX::Role::Parameterized;
parameter target_method => (
isa => "Str",
required => 1,
);
parameter prefix => (
isa => "Str",
required => 1,
default => "test",
);
role {
my $p = shift @_;
requires $p->target_method;
around $p->target_method => sub {
my ($orig, $self, $string) = @_;
return $p->prefix .":". $self->$orig($string);
};
}
1;
With:
use MooseX::Declare;
role MyParameterizableRole(
Str :$target_method!,
Str :$prefix! = "test"
) {
requires $target_method;
around "$target_method" (Str $string) {
return $self->$orig("$prefix: $string");
}
}
That's a lot less code, much less boilerplate! Both versions would
create a role that decorates a method of choice with some additional
text prepended to to start of the method return. You might use it like:
use MooseX::Declare;
class MyDecoratedClass {
method title(Str $string) {
return "'$string' is the title";
}
with 'MyParameterizableRole' => {
target_method => "title",
prefix => "FAQ",
};
}
You'd get a class with a decorated method. It would work like the following example:
my $object = MyDecoratedClass->new;
say $object->title("Using Moose");
## would output: "'FAQ:Using Moose' is the title"
The only thing that might confuse you is how the with
keyword is located after the method declaration. I know you typically
see the 'with' or 'extends' at the top of the class; this is possible
when using classic Moose
since if you declare a method with the 'sub' keyword that get's parsed
at compile time while the 'method' keyword is runtime. This means that
if the role you are aggregating requires a method (as this one does, via
the 'requires' keyword), that method must be declared prior to
including the role. You'll need to remember this when using roles with MooseX::Declare
and the method keyword. I know this might seem strange at first but
just remember the run-time versus compile-time thing and all will be
well. In fact, I find that it tends to clean up and clarify the
progression of activity in my classes, since instead of declaring all my
roles at the top of my class, they get stuck near the point where they
are being used. The addition benefit of adding the role and method late
is that you can use runtime variables and control syntax, something not
easy to do with compile time keywords. Generally speaking it makes the
dynamic aspect of Perl even more so.
For more information about what a parameterized role is be sure to see the documentation for MooseX::Role::Parameterized.
METHOD TRAITS
In order to understand method traits, you need to remember that a method in Moose is represented internally by the MOP (Meta Object Protocol) by an instance of Moose::Meta::Method (itself a subclass of Class::MOP::Method)
which mean that you can use roles to modify how a method works in the
same you'd do with any class. For example, you can add a role to a
method that automatically adds logging when a method is called. Your
role can alter or wrap any aspect of how a method instance works,
although one of the most typical and useful things to alter is how the
code reference that the method executes performs its job. That's all
that a method trait is, a role applied at runtime to a declared method.
It can take parameters, but unlike a parameterized role, the parameters
are applied to the method instance attributes. So a method trait is
just a regular Moose role.
For example, here's a role that wraps the return of any method in your HTML tag of choice:
use MooseX::Declare;
role MyMethodTrait {
use Scalar::Util 'weaken';
has tag => (is=>'ro', isa=>'Str', required=>1, default=>'div');
around wrap(ClassName $class: $code, %options) {
my ($method_obj, $weak_method_obj);
$method_obj = $weak_method_obj = $class->$orig(sub {
my ($self, $string) = @_;
my $title = $self->$code($string);
my $tag = $weak_method_obj->tag;
return "<$tag>$title</$tag>";
}, %options);
weaken($weak_method_obj);
return $method_obj;
}
}
So thats a bit complicated so I'll step through the code to make sure
you understand what's going on. Right now method traits are a
relatively new feature in MooseX::Declare
so there's not a lot of helper functions to make the more common cases
easy to do; perhaps in the future as people create more method traits we
will be able to modify how this works in order to reduce some
boilerplate.
use MooseX::Declare;
role MyMethodTrait {
This just sets you up to use MooseX::Declare and then declares that you are going to be making a Moose::Role.
use Scalar::Util 'weaken';
You'll need the weaken
function later in order to prevent the possibility of a circular reference causing a memory leak.
has tag => (is=>'ro', isa=>'Str', required=>1, default=>'div');
Here you add a new attribute called 'tag' which will be applied to
the consuming class. This will hold the name of the HTML tag which we
intent to wrap the return of the function with. I set a sane default.
around wrap(ClassName $class: $code, %options) {
The method wrap
is a class method which is responsible
for actually building the instance of your method class. Since it is a
class method you need to inform MooseX::Declare that the invocant is a ClassName
and not an Object
as it normally is. That's why we redefine the invocant of the method signature. You'll need to do this anytime your MooseX::Declare methods are called this way.
wrap
recieves two positional parameters, a CodeRef
which is the actual body of the method (the stuff inside the "{ ... }" for your method) and a Hash
of options. Don't worry about %options
for now, just realize that if you are going to wrap wrap
you'll need to pass them on to the underlying class.
my ($method_obj, $weak_method_obj);
$method_obj = $weak_method_obj = $class->$orig(sub {
So if you are already familiar with Moose method modifiers and how they work in MooseX::Declare this should not present too much difficulty. If you are not familiar with this you really need to take a look at Moose::Manual::MethodModifiers and the MooseX::Declare documentation. Basically we are going to invoke the wrapped method with an alternative CodeRef
that changes the behavior of method body. We assign the result to $method_obj
and $weak_method_obj
which have been predeclared so that we can close over $weak_method_obj
. We need the closure since until we call the original method (via $orig
) we don't yet have a method instance, thus we don't know the value of the tag
attribute. However, we jump through a few hoops here to avoid a
circular reference, that's why we doubly assign the result, since we
will need one unweaked reference to return at the end of the method.
$method_obj = $weak_method_obj = $class->$orig(sub {
my ($self, $string) = @_;
my $title = $self->$code($string);
my $tag = $weak_method_obj->tag;
return "<$tag>$title</$tag>";
}, %options);
Now inside the replacement method we will get access to $self
, which is going to be the actual instance of the object which contains the method (see MyParameterizedAndTrait
class definition below) and of course we get $string
which is the argument we are going to be wrapping in the HTML tag of choice. Now, by the time this code reference is invoked, $weak_method_obj
is going to be a real instance, so we can use it; in this case we are getting the tag attribute. Also, we have closed over $code
so that we can call it ourselves. Finally we return the modified results.
Okay, so I'm not going to a lot of trouble to make sure the tag
attributes is a valid HTML tag, but adding that bit would be easy with
any number of CPAN modules. You'd use the method trait like so:
use MooseX::Declare;
class MyParameterizedAndTrait {
method title(Str $string)
does MyMethodTrait(tag=>"p")
{
return "'$string' is the title";
}
with 'MyParameterizableRole' => {
target_method => "title",
prefix => "FAQ",
};
}
That 'does' (and its alias 'is') is similar to the way 'with' works
on the class level, it applies the role at runtime to the instance
representing the method. Parameters basically get passed down at
construction time. So, if you constructed the above class and called
the method 'title' like so, you get the following:
my $obj = MyParameterizedAndTrait->new;
say $obj->title("Hello World!");
## output is: "
'FAQ: Hello' is the title
"
So, right now its a bit tricky to write the method trait, although
actually using is is pretty clean and straightforward. Sometimes you
want to hide complexity this way, in which case a method trait is a good
solution.
PARAMETERIZABLE ROLES VERUS METHOD TRAITS
So far the given examples are a bit contrived, so let's try seeing
how we'd implement the same job using both techniques. Lets say you
commonly need to log method calls to STDERR (like via a warn
say) and you are tired of adding the following all the time:
method mymethod($arg) {
warn "mymethod was called with $arg";
}
Also, you'd like to regularize your approach and interface so that if
in the future you wanted to move to a logger with more features, such
as Log::Dispatch,
you can change that in one place, and not have to modified tons of
code. This is a trivial, yet not toy, bit of logic. So, how might we
do this with a parameterized role? Here's my take:
use MooseX::Declare;
role HasLogging(Str :$target_method!) {
require Data::Dumper;
requires $target_method;
before "$target_method" {
my ($self, @args) = @_;
warn "$target_method was called with " . Data::Dumper::Dumper \@args;
}
}
And here's how we might use it in a real class
use MooseX::Declare;
class LoggedMethodsParameterizableRole {
method my_method(Str $name, Int $age) {
return "Hi $name, you are $age years old";
}
with HasLogging => {target_method=>'my_method'};
}
So this is pretty straightforward, however if you want to apply it to
a bunch of methods code get a bit verbose, although it would probably
not be hard to change this to take an array of target methods. How
might we do this with a method trait instead? Here's one way:
use MooseX::Declare;
role HasLoggingMethodTrait {
use Scalar::Util 'weaken';
require Data::Dumper;
around wrap(ClassName $class: $code, %options) {
my ($method_obj, $weak_method_obj);
$method_obj = $weak_method_obj = $class->$orig(sub {
my ($self, @args) = @_;
my $target_method = $weak_method_obj->name;
warn "$target_method was called with " . Data::Dumper::Dumper(\@args);
return $self->$code(@args);
}, %options);
weaken($weak_method_obj);
return $method_obj;
}
}
So that's quite a bit more code, a lot of it boilerplate, but as I
said this functionality is relatively new and there's a lot of
opportunity for other people to jump in and improve or simplify the
process. In any case you'd use it like:
use MooseX::Declare;
class LoggedMethodsMethodTrait {
method my_method(Str $name, Int $age)
does HasLoggingMethodTrait {
return "Hi $name, you are $age years old";
}
}
So two ways to solve basically the same problem. Which one to use?
Personally I'd take the Parameterized role version for this particular
problem, since logging is something I might want to configure and change
often, and with a role I can apply late, perhaps even in configuration,
or with something like MooseX::Traits.
Currently you can't do this with a method trait. However for cases
where the trait is very tightly tied to the desired behavior of the
method, a method trait is probably the better choice.
Best practices continue to evolve and there's plenty of room open for
discussion, examples and contributions. Lets here what you think!
CONCLUSION
MooseX::Declare is more than just a sweeter syntax for Moose,
it aggregates and simplifies use of some valuable methods for building
better Object Oriented code. Both parameterized roles and method traits
offer techniques for reusing behavior and logic that can be
semantically more meaningful than alternatives.
Have Fun!
GET THIS CODE
All the code listed in the above article can be checked out from Github, over at: http://github.com/jjn1056/ParameterizedRolesAndMethodTraits.
That include the raw copy of this document and all the code examples
broken out into individual classes with test cases and a Makefile.PL of
all the required dependencies. A prepared distribution installable with
the standard Perl toolchain (CPAN, App::cpanminus) can be found here.
Free free to fork on Github and submit your changes or corrections!
THANKS
Thanks to the members of IRC #moose and Doy in particular for assistance to checking the code for this article.