Title: Moose Location: YAPC::Asia::2008 Presenter: Yuval Kogman Date: 2008 Theme: moose Moose Is Not ============ * Experimental * A toy * Just another accessor builder * A source filter * Perl black magic * Perl 6 in Perl 5 ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Moose Is ======== * A complete modern object framework for Perl ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ Moose Is ======== * Syntactic Sugar for `Class::MOP` * Rich ancestry * CLOS (Common Lisp Object System) * Smalltalk * Alces latifrons * Perl 6 * … * Stable & Production ready * Polite, incremental ✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------ 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.)
==============================
* `use Moose;`
* imports keywords
* `use strict; use warnings;`
* `@ISA = qw(Moose::Object) unless @ISA`
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
A Simple Moose Example (cont.)
==============================
* `has` declares attributes
* generates accessors
* `is => 'rw'` → read/write accessor
* `is => 'ro'` → read only accessor
* `writer`, `reader`
* `new` inherited from `Moose::Object`
##########
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.)
=====================================
* `default` is a
* code reference
* or non-reference (numbers, strings)
* used when no parameter is given to `new`
* `lazy` delays `default`
* called on first usage of `$object->staff`
* not inside `new`
##########
discusses default
non refs make accidental sharing hard
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Variations on a Moose Example (cont.)
=====================================
* `isa` specifies a type
* `Moose::Util::TypeConstraints`
* `Any, Item, Bool, Undef, Defined, Value, Num, Int, Str, Ref, ScalarRef, ArrayRef, HashRef, CodeRef, RegexpRef, GlobRef, FileHandle, Object and Role`
* Types don't need to exist
has 'date' => (isa => 'DateTime'); # DWIM
##########
isa, type constraints
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Typical Family
==============
* Types have a hierarchy
* `Item` ⊃ `Defined` ⊃ `Ref` ⊃ `Object`
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',
}
);
* manager `handles` certain methods for `Employee`
* `$emp->coworkers` == `$emp->manager->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' };
* Pre/Post hooks
* Get a copy of `@_`
* Return value is ignored
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
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
=================
* A role is like a…
* Java Interface: safe
* mixin: useful
* A role is for small reusable behaviors
* better than using a multiple inheritence
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Role of the Moose (cont.)
=========================
* Roles on the CPAN:
* `MooseX::Clone` - Flexible `clone` method
* `MooseX::Storage` - Flexible serialization
* `MooseX::Getopt` - `@ARGV` aware constructor
* `MooseX::LogDispatch` - `$self->logger->info("something happenned")`
* `MooseX::Param` - `param` method like `CGI.pm`'s,
##########
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);
* `with` adds roles into your class
* `Salaried::Hourly` was added to `Minion`
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Role of the Moose (cont.)
=========================
package Salaried;
use Moose::Role;
requires 'paycheck_amount';
* Just an interface
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
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;
}
* More than an interface
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Role of the Moose (cont.)
=========================
* More than Java Interfaces
* Interfaces are behavior "contracts"
* Roles can also have code
##########
roles can have attributes and methods
roles provide behavior, not just interface
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Role of the Moose (cont.)
=========================
* Role Composition
* Not inheritence
* Symmetric
* Unordered
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Role of the Moose (cont.)
=========================
* Role Composition
* Less ambiguity
* Compile time errors
* …And ways to fix them
##########
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;
}
}
* Not that common in practice
##########
Composition parameters
Easier conflict resolution
Finer grained control
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
MOPs Mean Cleanliness
=====================
* Moose is based on `Class::MOP`
* Metaobject Protocol for Perl 5
* "makes an object for everything"
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",
);
* Moose is just sugar
* The MOP does the hard work
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
The Metaclass Tango
===================
* Metaclasses control class behavior
has employees => (
metaclass => 'Collection::Array',
...
);
* custom attribute metaclasses
* change how attributes work
* Many customizable parts
* `Moose::Meta::Class`, `Moose::Meta::Attribute, ``Moose::Meta::Method`, `Moose::Meta::Method::Accessor` `Moose::Meta::Instance`, `Moose::Meta::Role`, `Moose::Meta::TypeConstraint`, …,
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Working in the Meta Frame
=========================
* `$work` project:
* CMS for a flash website
* Content is in XML
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Working in the Meta Frame (cont.)
=================================
* Step 1. use Moose
* Step 2. ???
* Step 3. Profit!
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Working in the Meta Frame (cont.)
=================================
* Step 2.1. Client's XML schemas → Moose classes
* Automatic class definitions
* High level objects in runtime
* XML storage backed
* SAX → Moose
* Moose → SAX
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Working in the Meta Frame (cont.)
=================================
* Step 2.2. Meta descriptions
* Extend the metaclasses
* Embed additional information
* field types
* access control
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Working in the Meta Frame (cont.)
=================================
* Step 2.3 Introspection goodness
* Generic web frontend
* Object introspection based
* HTML view
* Editing widgets
* Clean, extensible
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Drawbacks of Moose
==================
* Load time
* `MooseX::Compile` is in the works
* Some features are slow
* but you only pay for what you use
* Extending non-Hash based classes is tricky.
* but possible: `MooseX::GlobRef::Object`
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Benefits of Moose
=================
* Less boilerplate
* attribute storage/access
* construction
* destruction
* verification
* …
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Benefits of Moose (cont.)
=========================
* Shorter
* less reading
* less writing
* less code means fewer bugs
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Benefits of Moose (cont.)
=========================
* Less testing
* Moose is very well tested
* no need to check accessor behavior, etc
* focus on your code's purpose
* not that it is "assembled" correctly
* http://c2.com/cgi/wiki?IntentionNotAlgorithm
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Benefits of Moose (cont.)
=========================
* More readable
* declarative style is self documenting
* good signal to noise ratio
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Benefits of Moose (cont.)
=========================
* Meta object protocol
* Cleans up Perl's OO
* Provides introspection
* Enables powerful abstractions
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Benefits of Moose (cont.)
=========================
* It's the new black
* All the cool kids hang out on #moose
* Smart sounding buzzwords
* Chicks dig antlers
* Ruby is so 2007
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
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
===========
* Moose One Liners with `oose.pm`
perl -Moose -e 'has foo => ( is=> "rw" ); Class->new( foo => 1 )'
* Useful for testing if something works
* Helpful on IRC
* `Devel::REPL` is cooler though ;-)
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
MooseX::POE
===========
package Counter;
use MooseX::POE;
use MooseX::AttributeHelpers;
has count => (
traits => [qw(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();
* PoCos made easy
* Every object has a `POE::Session`
* `event` declares POE object states
✂------✂------✂------✂------✂------✂------✂------✂------✂------✂------
Fin
===
* Slides written by:
* Chris Prather
* Stevan Little
* Robert Boone
* Slides deleted by:
* Yuval Kogman