Title: An Intro to Moose Location: Minneapolis Perl Mongers Presenter: Chris Prather Organization: Infinity Interactive Date: September 12, 2007 Theme: moose What Moose Is Not ======= * An experiment or prototype * A toy * Just another accessor builder * A source filter or some other kind of Perl black magic * A implementation of Perl 6 in Perl 5 ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ What Moose Is ============= * A complete modern object framework for Perl * Syntactic Sugar for Class::MOP * Written by Stevan Little * Based on the work done by Stevan and others for Pugs and Perl 6 * Influenced by CLOS or Common Lisp Object System * Stable and used in Production systems * Stevan declared Moose as ready for prime time on April 3, 2007 at version 0.18 * We use Moose in large applications for household name clients * Currently at version 0.25 with 0.26 in SVN ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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;
* `use Moose;` * imports `has, extends, with, before, after, around, super, override, inner, augment` * turns on `use strict;` and `use warnings;` * `Carp::confess` and `Scalar::Util::blessed` * sets `@ISA` to `Moose::Object` (unless it's already set) ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ A Simple Moose Example (cont.) ==============================

package Person;
use Moose;

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

1;
* `has` declares attributes * Moose will automatically create the proper accessors * `is => 'rw'` create read/write accessor * `is => 'ro'` create read only accessor * Also note no constructor * `Moose::Object` provides one for us ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Variations on a Moose Example =============================

package Person;
use Moose;

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

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

1;
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Variations on a Moose Example (cont.) =====================================

has name => (
    is => 'rw', 
    isa => 'Str'
    default => 'Bob'
);
* `isa` sets up Type checking * Types are defined in `Moose::Util::TypeConstraints`. * Standard type constraints: `Any, Item, Bool, Undef, Defined, Value, Num, Int, Str, Ref, ScalarRef, ArrayRef, HashRef, CodeRef, RegexpRef, GlobRef, FileHandle, Object and Role` * Type constraints are subtype-able * Unknown types are assumed to be a Class and an anonymous subtype will be created

            has 'date' => (isa => 'DateTime'); # This will DWIM
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Variations on a Moose Example (cont.) =====================================

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

has staff => (
    is => 'ro',
    isa => 'ArrayRef',
    default => sub { [qw(Bob Alice Tim)] },
);
* `default` values are either ... * non-references (numbers, strings) * subroutine references * `default` sub is passed `$self` ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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
    );    
* `extends` declares the parent classes * `extends` is preferred over `use base` because it fully replaces the `@ISA` rather than extending it * it defines _the_ inheritance of the class, not an inheritance of the class * this gurantees no hidden surprises, like classes not properly inheriting from `Moose::Object` * it also happens to work better with the coming Perl 5.10 release ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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,
    );    
* Moose can convert from one type to another ... with a little help. ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Some Type of Coercion (cont.) =============================

    use Moose::Util::TypeConstraints;
* Make sure you use the TypeConstraints utilities

subtype 'Manager' 
    => as 'Object'
    => where { $_[0]->isa('Manager') };
* You have to define your subtype(s) * Types are global so you only need to do this once
    
coerce 'Manager' 
    => from 'Str'
    => via { Manager->new( name => $_) };        
* Then you tell Moose how to convert from one type to another

has manager =>  (
    ...
    coerce => 1,
);
* Finally you tell Moose which attributes to coerce ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Role of the Moose =================

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

    has 'weekly_hours' => (
        is => 'ro',
        isa => 'Int',
        default => 40,
    );
1;
* `with` adds a Role (`ProjectTeamRole`) to the Class (`TeamMember`) * A Role is a part of a Class that is composed into the class during Class creation ... basically a Mix-In * Similar in concept to Java's Interfaces * In fact Interfaces are a simplified case of Roles * Roles are much more powerful * `Moose::Object` provides a `does()` method to check objects for Roles

        $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);
    }
1;
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Role of the Moose (cont.) =========================

package ProjectTeamRole;
use Moose::Role;

    requires qw('weekly_hours');

* `use Moose::Role` * imports everything from Moose plus `requires` and `excludes` * `requires` says that the composing class must have a `weekly_hours` method * Note that Roles are only part of a Class * They cannot inherit from parent classes ... `extends` throws an exception * All other Moose keywords are deferred until the Class creation ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Role of the Moose (cont.) =========================

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);
}
* Roles can provide state * We add two more attributes to our class `project` and `project_hours` * Roles also provide mix-in methods * `log_hours` basically increments the `project_hours` by a given amount ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Role of the Moose (cont.) ========================= * There are several pre-baked Roles on the CPAN that provide various features * `MooseX::Storage` - Serialization to JSON or YAML * `MooseX::Daemonize` - Daemonization and handling of pid files * `MooseX::Getopt`- Command Line Parsing * `MooseX::IOC` - Support for Inversion of Control paradigm * `MooseX::Param` - Provide a `param()` method similar to CGI.pm's * And Several more in the Moose Repository waiting to be released * `MooseX::Workers` - forked process management for concurrently running tasks * `MooseX::Service` - Turns your class into a lazy service locator * `MooseX::LogDispatch` - Provide a standard Logging object ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Benefits of Moose ================= * Code is less tedious * no need to worry about the basic mechanics of OO like: * object initialization order * object destruction order * attribute storage, access and initialization * less tedium means many (sloppy) typo errors are all but eliminated * Code is shorter * Moose's declarative style allows you say more with less * less code == less bugs * Less low-level testing needed * no need to verify things which are already checked by the Moose test suite * tests can focus on what your class does, not that it is "assembled" correctly * Code becomes more descriptive * Moose's declarative style requires you to provide more descriptive information * Code describes your intentions more and OO mechanics less ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Drawbacks of Moose ================== * Moose has a fairly heavy compile-time cost. * Not appropriate for non-persistent environments (vanilla-CGI, etc.) * We are looking into using .pmc files to reduce this burden. * Some Moose features can be slow at times. * However speed is directly proportional to the amount of features used. * Nothing in life is free. * Extending non-Hash based classes is tricky. * The most common example is the IO::* family of class. * Stevan recommends Class::InsideOut or Object::InsideOut for this (or delegation, however ...). * Some delegation features do not play well with AUTOLOAD * AUTOLOAD is evil anyway :) ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Le Fin ====== * Special thanks to * Stevan and Robert Boone who's previous talks formed the foundation of this one * Matt Trout (mst), Yuval Kogman (nothingmuch), Robert Sedlacek (phaylon) and the rest of #moose

And you for coming