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:

  • parameter takes the same options as Moo::hasis is mandatory.

  • Inside the role block, always go through the $mop proxy ($mop->has, $mop->method). Calling has directly 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 and use MooX::Role::Parameterized::With; in the consumer.

  • parameter options follow Moo::has, so is is mandatory — Moose lets you omit it.

  • The role block receives ($params, $mop). Build the role through the $mop proxy: $mop->has, $mop->method, $mop->before, $mop->after, $mop->around, $mop->with, and $mop->requires.

  • There is no make_immutable step 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>