Title: A Practical Guide to Moose Location: Frozen Perl Workshop Presenter: Chris Prather Organization: Infinity Interactive Date: 2008-01-17 Theme: moose Location: Frozen Perl Workshop 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, Smalltalk, Java, BETA, OCaml, Ruby and more * 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.38 released Yesterday ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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 staff => (
	    is      => 'ro',
	    isa     => 'ArrayRef',
		lazy    => 1,
	    default => sub { [qw(Bob Alice Tim)] },
	);

1;
* `default` values are either ... * non-references (numbers, strings) * subroutine references * `default` sub is passed `$self` * `lazy` says not to run the default during object creation * default will be run the first time `staff` is called `$self->staff` ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Some Type of Digression ======================= * Parameterized Types

	has employees => (
		is => 'rw',
		isa => 'ArrayRef[Employee]',
	);
	
	has shopping_carts => (
		is => 'rw',
		isa => 'ArrayRef[ArrayRef[Items]]'
	);
* Union of Types

	has language => (
		is => 'rw',
		isa => 'English | Welsh | Scots | Gaelic',
	);	
	
	has member => (
		is => 'rw',
		isa => 'Employee | ArrayRef[Employee|Group]',
	);
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Some Type of Digression (cont.) =============================== * You can also use CPAN modules that check data types

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',
    );
* Use `Declare::Constraints` from CPAN * This example is taken straight from the Moose test suite * `t/200_examples/004_example_w_DCS.t` ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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 ===================== * Moose can also convert from one type to another ... with a little help.

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;
* 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 ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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,
	);
* Type Coercions can also apply to defaults, and to Arrays ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Conventional Delegates ======================

package Employee;
use Moose;
extends qw(Person);
    
    has manager =>  (
        is => 'ro',
        isa => 'Manager',
        handles => {
            manager_name => 'name',
            coworkers    => 'staff',
        }
    );    
* `handles` sets up a mapping between the current class, and the manager * `$self->manager_extension` will proxy `$self->manager->phone_extension ` ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Conventional Delegates (cont.) ============================== * Handles can also take an arrayref if you don't need to re-write the method names

	has phone => (
		...
		handles => [qw(number extension)],
	);
* Or a REGEXP or ROLE or CODE * But I haven't seen those used (yet) * and the coderef option is considered "funky" ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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',
        }
    )
* Use `MooseX::AttributeHelpers` from the CPAN * It provides an attribute metaclass * provides basic methods for standard data types * supplies `provides` parameter for attributes ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ UnConvetional Delegates (cont.) ===============================

    use Moose::Autobox;
    [ 1 .. 5 ]->map(sub { $_ * $_ }) # [ 1, 4, 9, 16, 25 ]
    'Hello World'->index('World')  # 6
* Using `Moose::Autobox` from CPAN * Allows you to call methods on references * Comes with delegates for all the major players ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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 Perl 5.10 ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Aspects of Method Modification ============================== * Moose provides the "Method Modifiers" `before`, `after` and `around`

	before 'employees' => sub { warn 'calling employees' };
* Executes just before the current method * It gets a copy of @_ * The return value is ignored

		after  'employees' => sub { warn 'finished calling employees' };
* Executes just after the current method * It gets a copy of @_ * The retun value is ignored ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Aspects of Method Modification (cont.) ====================================== * Around is a bit more complex

	around 'employees' => sub { 
		my ($next, $self, @args) = @_;
		...
		my @return = $self->$next(@args);
		....
		return @return;
	};
* Allows you to "monkey patch" methods automatically provided by Moose (or via a Role) * You control if the original method is called at all ($next) * The return value is *not* ignored. ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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);
	}
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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.) ========================= * Declare any additional attributes

	has project => (
		...
	);

	has project_hours => (
		...
	);
* And methods

	sub log_hours { ... };	
* This is where they differ from Java Interfaces * Interfaces only provide a "contract", Roles provide an implementation ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Role of the Moose (cont.) ========================= * Role Composition is unordered and can get quite complex

	
	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 
* By default the `log` would get dropped * This is by design, there are maths that explain it * I don't fully understand them * All is not lost though ... ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Role of the Moose (cont.) ========================= * You can specify that a Role is incompatible with another Role using `excludes`


	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
* We could set our Logger roles to exclude each other ... but it doesn't scale ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Role of the Moose (cont.) ========================= * Starting with Moose 0.34 `with` can also take parameters for composition

	package MyApp2;
	use Moose;
	with 'MyApp::Daemon' => { 
		excludes => 'log',
		alias => { 
			daemonize => 'run' 
		} 
	};
	with 'MyApp2::Logger';
* So we can `exclude` the `log` method from MyApp::Daemon * We'll also `alias` daemonize (from MooseX::Daemonize) to be our `run` method ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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::Param` - Provide a `param()` method similar to CGI.pm's * `MooseX::Workers` - forked process management for concurrently running tasks * `MooseX::LogDispatch` - Provide a standard Logging object ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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');
* You can define a custom Role to add more methods to Autoboxed variables ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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');
* Then later you can use the custom autobox features * taken from `t/003_p6_example.t` in the Moose::Autobox dist ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Metaclasses, Metaobjects, and the MOP (Oh My!) ============================================== * Moose is based on Class::MOP * Full Metaobject Protocol for Perl5 * Fancy words for makes an object for everything

	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; 
* The `meta` method returns the metaclass for every class and object * Metaclass provides introspection * `has_method` determine if the class has a given method * `compute_all_applicable_methods` returns a list of all the methods for a class (including inherited) * `has_attribute` same for an attribute * `compute_all_applicable_attributes` same here too ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Looking in From the Inside (cont.) ================================== * You can even create an entire class programmatically.

	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 =================== * Metaclasses allow you to override specific parts of how Moose does things * You've already seen them at work

	has employees => (
        metaclass => 'Collection::Array',
		...
    )
* `MooseX::AttributeHelpers` uses custom Attribute Metaclasses to do it's job * Attributes Metaclasses define how Attributes work * You can override nearly any part of Moose by providing the right metaclass ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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 * Unified Introspection * Moose gives you hooks to every part of the Perl OO Bestiary * And the Metaobject Protocol ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Drawbacks of Moose ================== * Moose has a fairly heavy compile-time cost. * Not appropriate for non-persistent environments (vanilla-CGI, etc.) * MooseX::Compile on the CPAN is a start on reducing this burden. * It pre-compiles everything to .pmc files * 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 ...). * That said, there is now MooseX::InsideOut for InsideOut objects * and MooseX::GlobRef::Object for the GlobRefs like IO::* * and MooseX::Singleton to enforce a Singleton pattern * Some delegation features do not play well with AUTOLOAD * AUTOLOAD is evil anyway :) ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ La Fin ====== * Special thanks to * Stevan and Robert Boone who's previous talks formed the foundation of this one * Matt Trout (mst), Yuval Kogman (nothingmuch), Jon Rockway (jrockway), Robert Sedlacek (phaylon), Ash Berlin (ash) and the rest of #moose * Robin Berjon(darobin), Joshua Jore (jjore), Christopher Humphries (chumphries) and others for the proofreading

And you for coming
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ -Moose ====== * Moose One Liners with `oose.pm`

	perl -Moose -e'has foo => (is=>q[rw]); Class->new(foo=>1)'
* Very useful for testing if something works * Nice for communicating ideas on IRC * My first (and only) source filter ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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 . "\n";
	    $self->count( $self->count + 1 );
	    $self->yield('increment') unless $self->count > 3;
	};

	Counter->new( count => 0 );
	POE::Kernel->run()
* Every class becomes a POE::Session * `events` are methods that can run asynchronously