While combining roles affords you a great deal of flexibility, individual roles have very little in the way of configurability. Core Moose provides "-alias" for renaming methods and "-excludes" for ignoring methods. These options are primarily for resolving role conflicts. Depending on how much of a purist you are, these options are solely for resolving role conflicts. See Moose::Cookbook::Roles::Restartable_AdvancedComposition for more about "-alias" and "-excludes".
Because roles serve many different masters, they usually provide only the least common denominator of functionality. To empower roles further, more configurability than "-alias" and "-excludes" is required. Perhaps your role needs to know which method to call when it is done processing. Or what default value to use for its "url" attribute.
Parameterized roles offer a solution to these (and other) kinds of problems.
with 'MyRole::InstrumentMethod' => { method_name => 'dbh_do', log_to => 'query.log', };
You can still combine parameterized roles. You just need to specify parameters immediately after the role they belong to:
with ( 'My::Parameterized::Role' => { needs_better_example => 1, }, 'My::Other::Role', );
We, like Moose itself, use Data::OptList to make sure that a list of role names and associated parameters is handled correctly.
parameter 'delegation' => ( isa => 'HashRef|ArrayRef|RegexpRef', predicate => 'has_delegation', );
You do have to declare what parameters you accept, just like you have to declare what attributes you accept for regular Moose objects.
One departure from "has" is that we create a reader accessor for you by default. In other words, we assume "is => 'ro'". We create this reader for convenience because generally the parameterized role is the only consumer of the parameters object, so data hiding is not as important than in the general case of ``has'' in Moose. If you do not want an accessor, you can use "is => 'bare'".
Each time you compose this parameterized role, the "role {}" block will be executed. It will receive a new parameter object and produce an entirely new role. That's the whole point, after all.
Due to limitations inherent in Perl, you must declare methods with "method name => sub { ... }" instead of the usual "sub name { ... }". Your methods may, of course, close over the parameter object. This means that your methods may use parameters however they wish!
parameter traits => ( isa => 'ArrayRef', default => sub { [] }, ); parameter type => ( isa => 'Str', default => 'Any', ); role { my $p = shift; has action => ( traits => $p->traits, isa => $p->type, ... ); };
parameter instrument_method => ( isa => 'Str', required => 1, ); role { my $p = shift; around $p->instrument_method => sub { ... }; };
parameter save_intermediate => ( isa => 'Bool', default => 0, ); role { my $p = shift; method process => sub { ... if ($p->save_intermediate) { ... } ... }; };
parameter format => ( isa => (enum ['Storable', 'YAML', 'JSON']), default => 'Storable', ); role { my $p = shift; if ($p->format eq 'Storable') { method freeze => \&Storable::freeze; method thaw => \&Storable::thaw; } elsif ($p->format eq 'YAML') { method freeze => \&YAML::Dump; method thaw => \&YAML::Load; } ... };
role { my $p = shift; my %args = @_; my $consumer = $args{consumer}; $consumer->find_attribute_by_name('stack') or confess "You must have a 'stack' attribute"; my $push = $consumer->find_method_by_name('push') or confess "You must have a 'push' method"; my $params = $push->parsed_signature->positional_params->params; @$params == 1 or confess "Your push method must take a single parameter"; $params->[0]->sigil eq '$' or confess "Your push parameter must be a scalar"; ... };
There is also a mailing list available for users of this distribution, at <http://lists.perl.org/list/moose.html>.
There is also an irc channel available for users of this distribution, at "#moose" on "irc.perl.org" <irc://irc.perl.org/#moose>.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.