NAME
MooX::Role::Parameterized::Cookbook - recipes for parameterized roles with Moo
DESCRIPTION
This is a documentation-only module. It collects worked recipes for MooX::Role::Parameterized, the Moo port of MooseX::Role::Parameterized.
Each recipe is backed by a runnable script in the distribution's examples/ directory, named at the end of the recipe. The author test xt/examples.t runs every one of those scripts, so the code shown here is kept honest against working programs.
If you have never used a parameterized role before, read the recipes in order. If you are porting code from Moose, jump to "RECIPE 4: PORTING FROM MooseX::Role::Parameterized".
RECIPE 1: YOUR FIRST PARAMETERIZED ROLE
Problem: you want a role that injects an attribute and a couple of methods, but the names depend on how the role is consumed.
Solution: declare a parameter, then build the role body from it.
package Counter;
use Moo::Role;
use MooX::Role::Parameterized;
parameter name => (
is => 'ro',
required => 1,
);
role {
my ( $params, $mop ) = @_;
my $name = $params->name;
$mop->has( $name => ( is => 'rw', default => sub {0} ) );
$mop->method(
"increment_$name" => sub {
my $self = shift;
$self->$name( $self->$name + 1 );
}
);
$mop->method(
"reset_$name" => sub {
my $self = shift;
$self->$name(0);
}
);
};
Consume it with MooX::Role::Parameterized::With, passing the parameter:
package Game::Wand;
use Moo;
use MooX::Role::Parameterized::With;
with Counter => { name => 'zapped' };
Game::Wand now has a zapped attribute plus increment_zapped and reset_zapped methods.
Two things to remember:
parametertakes the same options asMoo::has—isis mandatory.Inside the
roleblock, always go through the$mopproxy ($mop->has,$mop->method). Callinghasdirectly would install on the role instead of the consumer.
Runnable example: examples/basics.pl.
RECIPE 2: REQUIRED, TYPED, AND OPTIONAL PARAMETERS
Problem: you want some parameters mandatory, some optional, and some validated.
Solution: parameter accepts the full Moo::has specification, including required, isa, default, and predicate.
package Field;
use Moo::Role;
use MooX::Role::Parameterized;
parameter mandatory_attribute => (
is => 'ro',
required => 1,
);
parameter optional_attribute => (
is => 'ro',
predicate => 1,
);
role {
my ( $params, $mop ) = @_;
$mop->has( $params->mandatory_attribute => ( is => 'rw' ) );
if ( $params->has_optional_attribute ) {
$mop->has( $params->optional_attribute => ( is => 'rw' ) );
}
};
When a role declares at least one parameter, the $params argument is blessed into a generated Moo class. That is what enforces required and isa, and what gives you accessors such as $params->mandatory_attribute and the predicate $params->has_optional_attribute.
A role with no parameter declarations still works — there $params is a plain hash reference.
Runnable example: examples/parameters.pl.
RECIPE 3: APPLYING A ROLE SEVERAL TIMES
Problem: you want to apply the same parameterized role more than once to a single consumer, each time with different parameters.
Solution: pass an array reference of parameter sets. The with installed by MooX::Role::Parameterized::With applies the role once per set.
package KeyValue;
use Moo::Role;
use MooX::Role::Parameterized;
parameter attr => ( is => 'ro', required => 1 );
parameter method => ( is => 'ro', required => 1 );
role {
my ( $params, $mop ) = @_;
$mop->has( $params->attr => ( is => 'rw' ) );
$mop->method( $params->method => sub {1024} );
};
package Widget;
use Moo;
use MooX::Role::Parameterized::With;
with KeyValue => [
{ attr => 'width', method => 'compute_width' },
{ attr => 'height', method => 'compute_height' },
],
KeyValue => { attr => 'depth', method => 'compute_depth' };
Widget ends up with width, height, and depth attributes and the three compute_* methods. A single with call can mix the arrayref and hashref forms, and can name plain Moo, Moo::Role, and Role::Tiny roles alongside parameterized ones.
Runnable example: examples/applying-roles.pl.
RECIPE 4: PORTING FROM MooseX::Role::Parameterized
Problem: you have a role written with MooseX::Role::Parameterized and want to move it to Moo.
Solution: the DSL is deliberately close. The differences that matter:
Use
use MooX::Role::Parameterized;in the role anduse MooX::Role::Parameterized::With;in the consumer.parameteroptions followMoo::has, soisis mandatory — Moose lets you omit it.The
roleblock receives($params, $mop). Build the role through the$mopproxy:$mop->has,$mop->method,$mop->before,$mop->after,$mop->around,$mop->with, and$mop->requires.There is no
make_immutablestep to worry about.
The runnable example shows the Counter role — the canonical MooseX::Role::Parameterized example — ported to Moo and applied to two consumer classes.
Runnable example: examples/moosex-role-parameterized.pl.
RECIPE 5: A WORKED EXAMPLE — AN ARITHMETIC STREAM
Problem: something larger than a snippet — a parameterized role used as a building block in a small program.
Solution: build a lazy arithmetic-sequence stream. A plain Stream role defines the next protocol; a parameterized Stream::Sequence::Arithmetic role fills in first and code from its parameters.
package Stream::Sequence::Arithmetic;
use Moo::Role;
use MooX::Role::Parameterized;
with 'Stream';
role {
my ( $params, $mop ) = @_;
$mop->has( state => ( is => 'rw', predicate => 1 ) );
$mop->method( first => sub { $params->{first} } );
$mop->method(
code => sub {
my ( $self, $previous ) = @_;
return $previous + $params->{difference};
}
);
};
package Stream::TenPlusTen;
use Moo;
use MooX::Role::Parameterized::With;
with 'Stream::Sequence::Arithmetic' => { first => 10, difference => 10 };
This role declares no parameter, so $params is a plain hash reference — hence $params->{first} rather than $params->first (see Recipe 2). Stream::TenPlusTen yields 10, 20, 30, ...; the parameters first and difference decide which arithmetic sequence you get. The full program computes a running average over the first several terms.
This recipe is adapted from Perl Weekly Challenge 122.
Runnable example: examples/task-1-weekly-challenge-122.pl.
SEE ALSO
MooX::Role::Parameterized - the DSL itself
MooX::Role::Parameterized::With - the with override used by consumers
MooseX::Role::Parameterized - the Moose original
AUTHOR
Tiago Peczenyj <tiago.peczenyj+cpan@gmail.com>