Moose

Yuval Kogman

HAI FRENDS

WHY AM I HERE?

orz orz orz

So here I am

Moose

Moose Is Not

Moose Is

Moose Is

A Simple Example


package Person;

use strict;
use warnings;

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

    @args = %{$args[0]} if @args == 1;

    return bless {
        @args,
    }, $class;
}

sub name {
    my ($self, @args) = @_;
    $self->{name} = $args[0] if @args;
    return $self->{name};
}

sub age {
    my ($self, @args) = @_;
    $self->{age} = $args[0] if @args;
    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.)

A Simple Moose Example (cont.)

Now we’re going to discuss more features of the attributes

Variations on a Moose Example


package Manager;
use Moose;

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

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

Adds default, isa

Variations on a Moose Example (cont.)

discusses default

non refs make accidental sharing hard

Variations on a Moose Example (cont.)


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

isa, type constraints

Typical Family


        subtype 'Ref'
            => as 'Defined'
            => where {  ref($_) };

        subtype 'Object'
            => as 'Ref'
            => where { blessed($_) }

type hierarchy

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)],
);

Conventional Delegates (cont.)


has phone => (
    ...
    isa     => "Phone",
    handles => qr/^[a-z]w+$/,
);

Conventional Delegates (cont.)


has phone => (
    ...
    handles => "Dialing", # a role
);

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',
    },
);

Modified Methods


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

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

Modified Methods (cont.)


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

Modified Methods (cont.)


package Employee;
use Moose;

sub do_work {
    my $self = shift;

    $self->punch_in;

    inner(); # call subclass here

    $self->punch_out;
}

Modified Methods (cont.)


package Employee::Chef;
use Moose;

extends qw(Employee);

augment do_work => sub {
    my $self = shift;

    while ( @burgers ) {
        $self->flip_burger(shift @burgers);
    }
};

$chef->do_work; # punch in, flip burgers, punch out

Some Type of Coercion


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

class_type 'Manager';

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

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

Some Type of Coercion (cont.)


# import type constraint keywords
use Moose::Util::TypeConstraints;


# define Manager, a subtype of Object
class_type "Manager";


# define the conversion
... via { Manager->new( name => $_ ) }


# enable it per attribute
has manager => (
    ...
    coerce => 1,
);

breakdown of the example

class types are automatically created for all Moose classes

Some Type of Digression


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

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

Going to go into features of the type system for a bit

Parametrized types

Some Type of Digression (cont.)


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

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

Union types

Some Type of Digression (cont.)


package Foo;
use Moose;
use Moose::Util::TypeConstraints;

use Test::Deep qw(eq_deeply ...);

type 'SomethingTricky' => where {
    eq_deeply( $_, ... );
};

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

Test::Deep custom validator

Can use any validation from the CPAN

Some Parametrized Type of Coercion


use Moose::Util::TypeConstraints;

subtype 'ArrayRef[Employee]' => as 'ArrayRef';

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

has staff => (
    isa    => 'ArrayRef[Employee]',
    coerce => 1,
);

coerce parametrized ArrayRef[Employee] from ArrayRef[Str]

Role of the Moose

Role of the Moose (cont.)

Some examples of small reusable behaviors

Param is good for interacting with e.g. CGI::Expand or similar modules

Role of the Moose (cont.)


package Minion;
use Moose;

extends qw(Employee);

with qw(Salaried::Hourly);


package Boss;
use Moose;

extends qw(Employee);

with qw(Salaried::Monthly);

Role of the Moose (cont.)


package Salaried;
use Moose::Role;

requires 'paycheck_amount';

Role of the Moose (cont.)


package Salaried::Hourly;
use Moose::Role;

with qw(Salaried);

has hourly_rate => (
    isa => "Num",
    is  => "rw",
    required => 1,
);

has logged_hours => (
    isa => "Num",
    is  => "rw",
    default => 0,
);

# satisfy the Salaried interface:
sub paycheck_amount {
    my $self = shift;
    $self->logged_hours * $self->hourly_rate;
}

Role of the Moose (cont.)

roles can have attributes and methods roles provide behavior, not just interface

Role of the Moose (cont.)

Role of the Moose (cont.)

symmetric composition means no precedence - if two roles try to define the same thing you get a compile time error that needs to be resolved multiple inheritence silently assumes you want the first class

roles cause errors at compile time, unlike multiple inheritence

roles also provide easy ways to fix the errors

Role of the Moose (cont.)


package Ballet;
use Moose::Role;

sub dance {
    pirouette();
}

package Punk;
use Moose::Role

sub dance {
    MOSH!!!11one();
}

package Foo;
use Moose;

# KABOOM:
with qw(
    Punk
    Ballet
);

conflicts

Role of the Moose (cont.)


package Ent::Puppy;
use Moose;

with (
    Tree => {
        alias => {
            bark => "tree_bark",
        },
    },
    Dog => {
        alias => {
            bark => "bark_sound",
        }
    },
);

sub bark {
    my $self = shift;

    if ( $condition ) {
        $self->tree_bark;
    } else {
        $self->bark_sound;
    }
}

Composition parameters Easier conflict resolution Finer grained control

MOPs Mean Cleanliness


my $class = $obj->meta;       # $obj's metaclass
my $meta  = MyApp->meta;      # MyApp's metaclass
my $emo   = $obj->meta->meta; # even more meta!

warn  $obj->meta->name;

Looking in From the Inside


my $metaclass = $self->meta; 

$metaclass->superclasses;

$metaclass->linearized_isa;

$metaclass->has_method("foo");

$metaclass->compute_all_applicable_attributes;

# … lots more

simple introspection

Looking in From the Inside (cont.)


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

my $anon_meta = Moose::Meta::Class->create_anon_class( ... );

Classes can be created programmatically

Looking in From the Inside (cont.)


has foo => ( is => "rw" );

__PACKAGE__->meta->add_attribute(
    "foo",  
    is => "rw",
);

The Metaclass Tango


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

Working in the Meta Frame

Working in the Meta Frame (cont.)

Working in the Meta Frame (cont.)

Working in the Meta Frame (cont.)

Working in the Meta Frame (cont.)

Drawbacks of Moose

Benefits of Moose

Benefits of Moose (cont.)

Benefits of Moose (cont.)

Benefits of Moose (cont.)

Benefits of Moose (cont.)

Benefits of Moose (cont.)

Bonus Material

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',
);

Autobox (cont.)


use Units::Bytes;
use Moose::Autobox; # autoboxing is lexical

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 terabytes');

perl -Moose


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

MooseX::POE


package Counter;
use MooseX::POE;
use MooseX::AttributeHelpers;

has count => (
    metaclass => 'Counter',
    provides => { inc => "increment_count" },
);

sub START {
    shift->yield('increment');
}

event increment => sub {
    my $self = shift;

    warn "Count is now " . $self->count;

    $self->increment_count;
    $self->yield('increment') unless $self->count > 3;
};

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

Fin