Introduction to Moose

YAPC 2009

Moose Summed Up

Moose Background

Part 0: Moose Concepts

Classes

Class Example

package Person;
use Moose;

Attributes

Attributes

Attribute Example

package Person;
use Moose;

has first_name => ( is => 'rw' );

Methods

package Person;
use Moose;

sub greet { ... }

Roles

Roles

Role Example

package HasPermissions;
use Moose::Role;

has is_admin => ( is => 'rw' );

Role Example

And then ...

package Person;
use Moose;

with 'HasPermissions';

Method Modifiers

Before & After

before 'foo'
    => sub { warn 'About to call foo()' };

after  'foo'
    => sub { warn 'Leaving foo()' };

Around

around 'foo' => sub {
    my $real_foo = shift;
    my $self     = shift;

    warn 'Just before foo()';
    my @return =
        $self->$real_foo( @_, bar => 42 );

    return ( @return, 'modify return values' );
};

Type Constraints

Type Constraint Example

package Person;
use Moose;

has weight => (
    is  => 'ro',
    isa => 'Int',
);

# kaboom
Person->new( weight => 'fat' );

Delegation

Delegation

package Person;
use Moose;

has blog_uri => (
    is      => 'rw',
    isa     => 'URI',
    handles => { 'blog_hostname' => 'host' },
);

$person->blog_hostname;
# really calls $person->blog_uri->host

Constructors

Destructors

Moose Meta-API

Moose Meta-API

Why Moose?

With Moose

package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);

Without Moose

package Person;
use strict;
use warnings;
use Carp 'confess';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

sub last_name {
    my $self = shift;

    if (@_) {
        my $value = shift;
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $value"
            if ref($value);
        $self->{last_name} = $value;
    }

    return $self->{last_name};
}

Side by side

package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);
package Person;
use strict;
use warnings;
use Carp 'confess';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

sub last_name {
    my $self = shift;

    if (@_) {
        my $value = shift;
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $value"
            if ref($value);
        $self->{last_name} = $value;
    }

    return $self->{last_name};
}

Side by side

package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);
package Person;
use strict;
use warnings;
use Carp 'confess';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

sub last_name {
    my $self = shift;

    if (@_) {
        my $value = shift;
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $value"
            if ref($value);
        $self->{last_name} = $value;
    }

    return $self->{last_name};
}

Side by side

package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);
package Person;
use strict;
use warnings;
use Carp 'confess';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

sub last_name {
    my $self = shift;

    if (@_) {
        my $value = shift;
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $value"
            if ref($value);
        $self->{last_name} = $value;
    }

    return $self->{last_name};
}

Side by side

package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);
package Person;
use strict;
use warnings;
use Carp 'confess';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

sub last_name {
    my $self = shift;

    if (@_) {
        my $value = shift;
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $value"
            if ref($value);
        $self->{last_name} = $value;
    }

    return $self->{last_name};
}

Side by side

package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);
package Person;
use strict;
use warnings;
use Carp 'confess';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

sub last_name {
    my $self = shift;

    if (@_) {
        my $value = shift;
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $value"
            if ref($value);
        $self->{last_name} = $value;
    }

    return $self->{last_name};
}

Side by side

package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);
package Person;
use strict;
use warnings;
use Carp 'confess';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

sub last_name {
    my $self = shift;

    if (@_) {
        my $value = shift;
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $value"
            if ref($value);
        $self->{last_name} = $value;
    }

    return $self->{last_name};
}

Side by side

5 lines 21 lines
92 characters 741 characters
package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);
package Person;
use strict;
use warnings;
use Carp 'confess';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

sub last_name {
    my $self = shift;

    if (@_) {
        my $value = shift;
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $value"
            if ref($value);
        $self->{last_name} = $value;
    }

    return $self->{last_name};
}

Typo?

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = {};

    if (exists $args{last_name}) {
        confess "Attribute (last_name) does not pass the type constraint because: "
                . "Validation failed for 'Str' with value $args{last_name}"
            if ref($args{last_name});
        $self->{last_nane} = $args{last_name};
    }

    return bless $self, $class;
}

Typo?

if (exists $args{last_name}) {
    confess "Attribute (last_name) does not pass the type constraint because: "
            . "Validation failed for 'Str' with value $args{last_name}"
        if ref($args{last_name});
    $self->{last_nane} = $args{last_name};
}

Typo?

$self->{last_nane} = $args{last_name};

Typo?

$self->{last_nane}

Why Moose?

package Person;
use Moose;

has last_name => (
    is  => 'rw',
    isa => 'Str',
);

Part 1: Moose Classes

Moose Classes

Moose.pm and Your Class

package Person;
use Moose;

What Moose::Object Provides

extends

package Employee;
use Moose;
extends 'Person';

extends

WRONG

package EvilEmployee;
use Moose;
extends 'Person';
extends 'Thief';

RIGHT

package EvilEmployee;
use Moose;
extends 'Person', 'Thief';

Extending un-Moose-y Parents

package My::LWP;
use Moose;
extends 'LWP';

overrides and super

overrides and super

package Employee;
use Moose;

extends 'Person';

overrides work => sub {
    my $self = shift;

    die "Pay me first" unless $self->got_paid;
    super();
};

Caveat super

Attributes (Part 1)

Read-write attributes

package Person;
use Moose;

has first_name => ( is => 'rw' );

my $person =
    Person->new( first_name => 'Dave' );

$person->first_name('Stevan');
print $person->first_name; # Stevan

Read-only attributes

package Person;
use Moose;

has first_name => ( is => 'ro' );

my $person =
    Person->new( first_name => 'Dave' );

$person->first_name('Stevan');
print $person->first_name; # Dave

There is More to Come

Cleaning Up Moose Droppings

package Person;
use Moose;

# true
Person->can('extends');

Cleaning Up Moose Droppings

package Person;
use Moose;

...

no Moose;

# false
Person->can('extends');

No Moose

Immutability

package Person;
use Moose;

__PACKAGE__->meta->make_immutable;

What make_immutable does

When to Immutabilize?

Classes Summary

Exercises

$ cd exercises
$ perl bin/prove -lv t/00-prereq.t

Missing anything? Install it. (see tarballs/)

# perl bin/prove -lv t/01-classes.t

Iterate til this passes all its tests

Part 2: Roles

Just What Is a Role?

Roles Can Have State and Behavior

package HasPermissions;
use Moose::Role;

# state
has access_level => ( is => 'rw' );

# behavior
sub can_access {
    my $self     = shift;
    my $required = shift;

    return $self->access_level >= $required;
}

Roles Can Define Interfaces

package Printable;
use Moose::Role;

requires 'as_string';

Roles Can Do All Three

package Printable;
use Moose::Role;

requires 'as_string';

has has_been_printed => ( is => 'rw'  );

sub print {
    my $self = shift;
    print $self->as_string;
    $self->has_been_printed(1);
}

Classes Consume Roles

package Person;
use Moose;

with 'HasPermissions';

Classes Consume Roles

my $person = Person->new(
    first_name => 'Kenichi',
    last_name => 'Asai',
    access_level => 42,
);

print $person->full_name
    . ' has '
    . $person->can_access(42)
        ? 'great power'
        : 'little power';

Roles in Practice

In Other Words ...

package Person;
use Moose;

with 'Printable';

In Other Words ...

package Person;
use Moose;

with 'Printable';

has has_been_printed => ( is => 'rw'  );

sub print {
    my $self = shift;
    print $self->as_string;
    $self->has_been_printed(1);
}

Except

if ( Person->does('Printable') ) { ... }

# or ...

if ( Person->meta->does('Printable') ) { ... }

These Names Are the Same

Conflicts Between Roles

Conflict Example

package IsFragile;
use Moose::Role;

sub break { ... }

package CanBreakdance;
use Moose::Role;

sub break { ... }

Conflict Example

package FragileDancer;
use Moose;

with 'IsFragile', 'CanBreakdance';

Conflict Resolution

Method Aliasing

package FragileDancer;
use Moose;

with 'IsFragile' =>
         { alias =>
               { break => 'break_bone' } },
     'CanBreakdance' =>
         { alias =>
               { break => 'break_it_down' } };

Method Exclusion

package FragileDancer;
use Moose;

with 'IsFragile' =>
         { alias =>
               { break => 'break_bone' },
           exclude => 'break' },
     'CanBreakdance' =>
         { alias =>
               { break => 'break_dance' },
           exclude => 'break' };

And then ...

package FragileDancer;
use Moose;

sub break {
    my $self = shift;

    $self->break_dance;
    if ( rand(1) < 0.5 ) {
        $self->break_bone;
    }
}

Still Full of Fail

Hot Role-on-Role Action

package Comparable;
use Moose::Role;

requires 'compare';

Hot Role-on-Role Action

package TestsEquality;
use Moose::Role;

with 'Comparable';

sub is_equal {
    my $self = shift;
    return $self->compare(@_) == 0;
}

And then ...

package Integer;
use Moose;

with 'TestsEquality';

# Satisfies the Comparable role
sub compare { ... }

Integer->does('TestsEquality'); # true
Integer->does('Comparable'); # also true!

Name Conflicts Between Roles

package HasSubProcess;
use Moose::Role;

sub execute { ... }

package Killer;
use Moose::Role;

with 'HasSubProcess';

sub execute { ... }

Delayed Conflict

package StateOfTexas;
with 'Killer';

Roles as Interfaces

The Attribute Gotcha

package HasSize;
use Moose::Role;

requires 'size';

package Shirt;
use Moose;

with 'HasSize';

has size => ( is => 'ro' );

The Attribute Gotcha Workaround

package HasSize;
use Moose::Role;

requires 'size';

package Shirt;
use Moose;

has size => ( is => 'ro' );

with 'HasSize';

Compile-time Is a Lie

Enforcing Roles

package Comparison;
use Moose;

has [ 'left', 'right' ] => (
    is   => 'ro',
    does => 'Comparable',
);

Roles Can Be Applied to Objects

use Moose::Util qw( apply_all_roles );

my $fragile_person = Person->new( ... );
apply_all_roles( $fragile_person, 'IsFragile' );

Roles Are Dirty Too

package Comparable;
use Moose::Role;

requires 'compare';

no Moose::Role;

The Zen of Roles

Abstract Examples

Real Examples

Real Examples

Exercises

$ cd exercises
# perl bin/prove -lv t/02-roles.t

Iterate til this passes all its tests