Introduction to Moose

YAPC 2009

Moose Summed Up

Part 0: Moose Concepts

Moose background

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

  • Moose creates new() for you
  • Provide an optional BUILDARGS() and BUILD()

Destructors

  • Provide an optional DEMOLISH()

Moose Meta-API

  • Answers questions like ...
    • What methods does this class have?
    • What are its parents?
    • What attributes does it have (including inherited attributes)?
    • What roles does it do?
    • Much, much, more

Moose Meta-API

  • Not just for introspection ...
    • Add methods, attributes, roles, etc
    • Extend and alter core features

Why Moose?

  • A quick bit of propoganda ...

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 classes are Perl packages which use Moose

Moose.pm and Your Class

package Person;
use Moose;
  • Moose.pm provides declarative sugar
  • Turns on strict and warnings
  • Creates metaclasses for your class: Person->meta
  • Moose classes automatically inherit from Moose::Object

What Moose::Object Provides

  • Constructor - new()
  • Calls your BUILDARGS() and/or BUILD()
  • Calls your DEMOLISH during object destruction

extends

  • extends is sugar for declaring parent classes
package Employee;
use Moose;
extends 'Person';

extends

  • Each call to extends resets your parents

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';
  • No Moose::Object, so ...
    • No attribute-handling new()
    • No BUILDARGS() or BUILD()
    • No DEMOLISH()
  • But see MooseX::NonMoose for a workaround

overrides and super

  • overrides is another method modifier
  • An alternative to Perl's 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

  • Mostly like $self->SUPER::work(@_)
  • But cannot change @_!
  • Binds the parent's method at compile time

Attributes (Part 1)

  • has 'foo'
  • Use is => 'ro' or is => 'rw'
  • Attributes without "is" have no accessors

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

  • Attributes have a lot of features

Cleaning Up Moose Droppings

package Person;
use Moose;

# true
Person->can('extends');
  • Not very hygienic

Cleaning Up Moose Droppings

package Person;
use Moose;

...

no Moose;

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

No Moose

  • no Moose at the end of a package is a best practice
  • Just do it

Immutability

  • Stevan's Incantation of Fleet-Footedness
package Person;
use Moose;

__PACKAGE__->meta->make_immutable;

What make_immutable does

  • Magic
  • Uses eval to "inline" a constructor
  • Memoizes a lot of meta-information
  • Makes loading your class slower
  • Makes object creation much faster

When to Immutabilize?

  • Almost always
  • Startup time vs execution time

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