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.
- Learning Perl
See the web site "Learning Perl", http://learn.perl.org
- Moose Tutorial
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.
- Moose Homepage
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.
Just one clarification - in the examples above:
return $p-prefix .":". $self-$orig($string);
is later in the MooseX::Declare version transformed into:
return $self-$orig("$prefix: $string");
does that mean that theMooseX::Declare peeks into the parameter and parses it to remove$prefix: and them attach it to the output of$self-$orig($string); ?
Posted by: zby | 08/03/2010 at 10:47 AM
Hey,Good catch! I'll update on Github shortly. The two returns do indeed function slightly different. The first one: return $p-prefix .":". $self-$orig($string);Calls the original method with the original arguments ($string), pre-pends the prefix and then returns the lot. return $self-$orig("$prefix: $string");Sends a modified argument to the original method and then returns that. Whether they return the same value or not will depend on the what the underlying function does.Thanks! I'll fix it up shortly.
Posted by: John Napiorkowski | 08/03/2010 at 08:21 PM
The blog looks good. Maybe improving the website design will make it look more professional and earn you more followers. Keep up the good work!
Posted by: Danzel | 09/20/2011 at 12:32 PM