A Practical Guide to Moose

Chris Prather

Infinity Interactive

What Moose Is Not

What Moose Is

A Simple Example


package Person;
use strict;
use warnings;

sub new {
    my ($class) = @_;

    return bless {
        name => '',
        age  => undef,
    }, $class;
}

sub name {
    my ($self, $name) = @_;
    $self->{'name'} = $name if $name;
    return $self->{'name'};
}

sub age {
    my ($self, $age) = @_;
    $self->{'age'} = $age if $age;
    return $self->{'age'}; 
}

1;

A Simple Moose Example


package Person;
use Moose;

    has name => (is => 'rw');
    has age  => (is => 'rw');

1;

A Simple Moose Example (cont.)


package Person;
use Moose;

    has name => (is => 'rw');
    has age  => (is => 'rw');

1;

Variations on a Moose Example


package Person;
use Moose;

    has name => (
        is => 'rw', 
        isa => 'Str'
        default => 'Bob'
    );

    has staff => (
        is      => 'ro',
        isa     => 'ArrayRef',
        lazy    => 1,
        default => sub { [qw(Bob Alice Tim)] },
    );

1;

Variations on a Moose Example (cont.)


has name => (
    is => 'rw', 
    isa => 'Str'
    default => 'Bob'
);

            has 'date' => (isa => 'DateTime'); # This will DWIM

Some Type of Digression


    has employees => (
        is => 'rw',
        isa => 'ArrayRef[Employee]',
    );

    has shopping_carts => (
        is => 'rw',
        isa => 'ArrayRef[ArrayRef[Items]]'
    );

    has language => (
        is => 'rw',
        isa => 'English | Welsh | Scots | Gaelic',
    );  

    has member => (
        is => 'rw',
        isa => 'Employee | ArrayRef[Employee|Group]',
    );

Some Type of Digression (cont.)


package Foo;
use Moose;
use Moose::Util::TypeConstraints;
use Declare::Constraints::Simple -All;

# define your own type ...
type 'HashOfArrayOfObjects' 
    => IsHashRef(
        -keys   => HasLength,
        -values => IsArrayRef( IsObject ));    

has 'bar' => (
    is  => 'rw',
    isa => 'HashOfArrayOfObjects',
);

Some Type of Digression (cont.)



package Foo;
use Moose;
use Moose::Util::TypeConstraints;
use Test::Deep qw(eq_deeply array_each subhashof ignore);

# define your own type ...
type 'ArrayOfHashOfBarsAndRandomNumbers' 
    => where {
        eq_deeply($_, 
            array_each(
                subhashof({
                    bar           => Test::Deep::isa('Bar'),
                    random_number => ignore()
                })
            )
        )
    };    

has 'bar' => (
    is  => 'rw',
    isa => 'ArrayOfHashOfBarsAndRandomNumbers',
);

Some Type of Coercion


package Employee;
use Moose;
use Moose::Util::TypeConstraints;
extends qw(Person);

    subtype 'Manager' 
        => as 'Object'
        => where { $_[0]->isa('Manager') };

    coerce 'Manager' 
        => from 'Str'
        => via { Manager->new( name => $_) };

    has manager =>  (
        is => 'ro',
        isa => 'Manager',
        required => 1, 
        coerce => 1,
    );    

Some Type of Coercion (cont.)


    use Moose::Util::TypeConstraints;

    subtype 'Manager' 
        => as 'Object'
        => where { $_[0]->isa('Manager') };
    
    coerce 'Manager' 
        => from 'Str'
        => via { Manager->new( name => $_) };        

    has manager =>  (
        ...
        coerce => 1,
    );

Some Type of Coercion (cont.)



use Moose::Util::TypeConstraints;   
subtype 'ArrayRef[Employee]' => as 'ArrayRef';

coerce 'ArrayRef[Employee]' 
    => from 'ArrayRef[Str]' 
    => via { [ map { Employee->new( name => $_ ) } @$_ ] };

has staff => (
    is         => 'ro',
    isa        => 'ArrayRef[Employee]',
    lazy       => 1,
    default    => sub { [qw(Bob Alice Tim)] },
    coerce     => 1,
);

Conventional Delegates


package Employee;
use Moose;
extends qw(Person);

has manager =>  (
    is => 'ro',
    isa => 'Manager',
    handles => {
        manager_name => 'name',
        coworkers    => 'staff',
    }
);

Conventional Delegates (cont.)


    has phone => (
        ...
        handles => [qw(number extension)],
    );

UnConventional Delegates


package Company;
use Moose;
use MooseX::AttributeHelpers;

    has employees => (
        metaclass => 'Collection::Array',
        isa => 'ArrayRef[Employees]',
        is => 'rw',
        provides => {
            push  => 'add_employee',
            pop   => 'remove_employee',
            count => 'number_of_employees',
            empty => 'any_employees',
        }
    )

UnConvetional Delegates (cont.)


    use Moose::Autobox;
    [ 1 .. 5 ]->map(sub { $_ * $_ }) # [ 1, 4, 9, 16, 25 ]
    'Hello World'->index('World')  # 6

Extending the Example


package Person;
use Moose;

    has name => (is => 'rw');
    has age  => (is => 'rw');


package Employee;
use Moose;
extends qw(Person);

    has manager =>  (
        is => 'ro',
        isa => 'Manager',
        required => 1, # Everybody has to have a manager
    );

package Manager;
use Moose;
extends qw(Employee);

    has staff => (
        is => 'rw',
        isa => 'ArrayRef',
        default => sub { [] },
    );

Extending the Example (cont.)


package Employee;
use Moose;
extends qw(Person);

    has manager =>  (
        is => 'ro',
        isa => 'Manager',
        required => 1, # Everybody has to have a manager
    );    

Aspects of Method Modification


    before 'employees' => sub { warn 'calling employees' };


        after  'employees' => sub { warn 'finished calling employees' };

Aspects of Method Modification (cont.)


    around 'employees' => sub { 
        my ($next, $self, @args) = @_;
        ...
        my @return = $next->(@args);
        ....
        return @return;
    };

Role of the Moose


package TeamMember;
use Moose;
extends qw(Employee);
with qw(ProjectTeamRole);

    has 'weekly_hours' => (
        is => 'ro',
        isa => 'Int',
        default => 40,
    );
1;

        $emp->does('ProjectTeamRole'); # true for TeamMembers

Role of the Moose (cont.)



    package ProjectTeamRole;
    use Moose::Role;

    requires qw('weekly_hours');

    has project => (
        is       => 'ro',
        isa      => 'Str',
        required => 1,
    );

    has project_hours => (
        is      => 'rw',
        isa     => 'Int',
        default => 0,
    );

    sub log_hours {
        my ( $self, $hours ) = @_;
        my $total = $self->project_hours + $hours;
        $self->project_hours($total);
    }

Role of the Moose (cont.)


package ProjectTeamRole;
use Moose::Role;

    requires qw('weekly_hours');

Role of the Moose (cont.)


    has project => (
        ...
    );

    has project_hours => (
        ...
    );


    sub log_hours { ... };

Role of the Moose (cont.)



package MyApp::Daemon;
use Moose::Role;
with 'MooseX::LogDispatch'; # provides log() method
with 'MooseX::Daemonize'; 

package MyApp2;
use Moose;

with 'MyApp::Daemon'; # provides log() method
with 'MyApp2::Logger'; # also provides log() method

Role of the Moose (cont.)



package Molecule::Organic;
use Moose::Role;
excludes 'Molecule::Inorganic';

package Molecule::Inorganic;
use Moose::Role;     

package My::ScienceProject;
use Moose;
with qw(Molecule::Organic Molecule::Inorganic); # dies

Role of the Moose (cont.)


    package MyApp2;
    use Moose;
    with 'MyApp::Daemon' => { 
        excludes => 'log',
        alias => { 
            daemonize => 'run' 
        } 
    };
    with 'MyApp2::Logger';

Role of the Moose (cont.)

Return of the Autobox


package Units::Bytes;
use Moose::Role;
use Moose::Autobox;

sub bytes     { $_[0]                   }    
sub kilobytes { $_[0] * 1024            }
sub megabytes { $_[0] * 1024->kilobytes }
sub gigabytes { $_[0] * 1024->megabytes }
sub terabytes { $_[0] * 1024->gigabytes }

Moose::Autobox->mixin_additional_role(SCALAR => 'Units::Bytes');

Return of the Autobox (cont.)


use Units::Bytes;
use Moose::Autobox;

is(5->bytes,     5,             '... got 5 bytes');
is(5->kilobytes, 5120,          '... got 5 kilobytes');
is(2->megabytes, 2097152,       '... got 2 megabytes');
is(1->gigabytes, 1073741824,    '... got 1 gigabyte');
is(2->terabytes, 2199023255552, '... got 2 terabyte');

Metaclasses, Metaobjects, and the MOP (Oh My!)


    my $class = $obj->meta; # $obj's metaclass
    my $meta = MyApp->meta; # MyApp's metaclass
    my $really_meta = $obj->meta->meta # $obj's metaclass's metaclass

print $obj->meta->name; #print $obj's class' name

Looking in From the Inside


    my $class = $self->meta; 

Looking in From the Inside (cont.)


    Class::MOP::Class->create('Bar' => (
          version      => '0.01',
          superclasses => [ 'Foo' ],
          attributes => [
              Class::MOP:::Attribute->new('$bar'),
              Class::MOP:::Attribute->new('$baz'),
          ],
          methods => {
              calculate_bar => sub { ... },
              construct_baz => sub { ... }
          }
      ));

The Metaclass Tango


    has employees => (
        metaclass => 'Collection::Array',
        ...
    )

Benefits of Moose

Drawbacks of Moose

Le Fin

And you for coming

-Moose


    perl -Moose -e'has foo => (is=>q[rw]); Class->new(foo=>1)'

MooseX::POE



package Counter;
use MooseX::POE;

has count => (
    isa     => 'Int',
    is      => 'rw',
);

sub START {
    my ($self) = @_;
    $self->yield('increment');
}

event increment => sub {
    my ($self) = @_;
    print "Count is now " . $self->count . "
";
    $self->count( $self->count + 1 );
    $self->yield('increment') unless $self->count > 3;
};

Counter->new( count => 0 );
POE::Kernel->run()