Introduction to Moose
YAPC 2009
Moose Summed Up
- Declarative OO sugar
- Introspectable
- Extensible (MooseX::* on CPAN)
Moose Background
- Created by Stevan Little, first released in 2006
- Moose builds on Perl 5's native OO
- Borrows ideas from other languages, notably Perl 6
- Provides semantics for common operations
Part 0: Moose Concepts
Classes
-
Classes have ...
- Attributes
- Methods
- Superclasses
- Method modifiers
- Constructor and destructor
- One metaclass object
- Classes do roles
Class Example
package Person;
use Moose;
- Poof, a Moose-based class!
Attributes
- Aka property, slot, field, member variable
- A piece of data owned by an object
Attributes
-
Attributes have ...
- Access-control (read-only vs read-write)
- An optional type
- Accessor methods
- Delegation methods
- Optional default value
- Many more features
- Stored in the object, but don't worry about that
Attribute Example
package Person;
use Moose;
has first_name => ( is => 'rw' );
Methods
- Nothing fancy here, just Perl subroutines
package Person;
use Moose;
sub greet { ... }
Roles
- Classes do (or consume) roles
- Similar to mixins and Java interfaces
Roles
- Like classes, can have attributes, methods, do roles
- Roles can require methods
- Roles are composed (flattened) into classes
Role Example
package HasPermissions;
use Moose::Role;
has is_admin => ( is => 'rw' );
Role Example
And then ...
package Person;
use Moose;
with 'HasPermissions';
Method Modifiers
- AKA advice
- "Before foo(), do this first"
- "Do this after foo()
- "Put this code around foo()"
Before and 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
- NOT A FULL-BLOWN TYPE SYSTEM!
- But still darn useful
- Constrain attribute values
- Coerce from other types
Type Constraint Example
package Person;
use Moose;
has weight => (
is => 'ro',
isa => 'Int',
);
# kaboom
Person->new( weight => 'fat' );
Delegation
- Attributes can define delegations
- Lets you hide some implementation details
- Fewer objects to chase around
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 propaganda ...
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
BUILDARGS
- Takes
@_
, returns a hash reference of attribute names/value
- Accepts a hash or hashref; throws otherwise
- Provide your own for other cases
- Always call
$class->SUPER::BUILDARGS(@_)
as a fallback!
BUILDARGS Example
package Person;
use Moose;
sub BUILDARGS {
my $class = shift;
if ( @_ == 1 && ! ref $_[0] ) {
return { ssn => $_[0] };
}
return $class->SUPER::BUILDARGS(@_);
}
Person->new('123-45-6789')
BUILD
- Called after object is created, before
new
returns
- Chance to do more complex validation, set complex attributes
- Called in reverse inheritance order, parents to children
- Return value is ignored
BUILD Example
package Person;
use Moose;
sub BUILD {
my $self = shift;
if ( $self->country_of_residence
eq 'USA' ) {
die 'All US residents'
. ' must have an SSN'
unless $self->has_ssn;
}
}
DEMOLISH
- Like
DESTROY
, but Moose makes sure all DEMOLISH
methods in a hierarchy are called
- Called in normal inheritance order, children to parents
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
override
and super
override
is another method modifier
- An alternative to Perl's
SUPER::
override
and super
package Employee;
use Moose;
extends 'Person';
override 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
Minimal Attributes
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');
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
Classes Summary
use Moose
Class->meta
Moose::Object
base class
extends
, override
, and super
- Simple attributes:
has
, is => 'ro'
, & is => 'rw'
no Moose
__PACKAGE__->meta->make_immutable
Questions?
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
Part 2: Roles
Just What Is a Role?
- Mixin? Interface? Trait?
- Yes ... and more!
Roles Can Have State and Behavior
package HasPermissions;
use Moose::Role;
# state
has access_level => ( is => 'rw' );
# behavior
sub can_access {
my $self = shift;
my $required = shift;
return $self->access_level >= $required;
}
Roles Can Define Interfaces
package Printable;
use Moose::Role;
requires 'as_string';
Roles Can Do All Three
package Printable;
use Moose::Role;
requires 'as_string';
has has_been_printed => ( is => 'rw' );
sub print {
my $self = shift;
print $self->as_string;
$self->has_been_printed(1);
}
Classes Consume Roles
package Person;
use Moose;
with 'HasPermissions';
Classes Consume Roles
my $person = Person->new(
first_name => 'Kenichi',
last_name => 'Asai',
access_level => 42,
);
print $person->full_name
. ' has '
. $person->can_access(42)
? 'great power'
: 'little power';
Roles in Practice
- Consuming a role =~ inlining the role
In Other Words ...
package Person;
use Moose;
with 'Printable';
In Other Words ...
package Person;
use Moose;
with 'Printable';
has has_been_printed => ( is => 'rw' );
sub print {
my $self = shift;
print $self->as_string;
$self->has_been_printed(1);
}
Except
- Role consumption is introspectable
if ( Person->does('Printable') ) { ... }
# or ...
if ( Person->meta->does('Printable') ) { ... }
These Names Are the Same
- What if a role and class define the same method?
- A class's local methods win over the role's
- The role's methods win over the class's inherited methods
Conflicts Between Roles
- Two roles with a method of the same name
- Generates a compile-time error when consumed by a class
Conflict Example
package IsFragile;
use Moose::Role;
sub break { ... }
package CanBreakdance;
use Moose::Role;
sub break { ... }
Conflict Example
package FragileDancer;
use Moose;
with 'IsFragile', 'CanBreakdance';
Conflict Resolution
- The consuming class must resolve the conflict by implementing the method
- Can use some combination of method exclusion and aliasing
Method Aliasing
package FragileDancer;
use Moose;
with 'IsFragile' =>
{ alias =>
{ break => 'break_bone' } },
'CanBreakdance' =>
{ alias =>
{ break => 'break_it_down' } };
- Renames the roles' methods
- Still conflicts, need to
exclude
as well
Method Exclusion
package FragileDancer;
use Moose;
with 'IsFragile' =>
{ alias =>
{ break => 'break_bone' },
exclude => 'break' },
'CanBreakdance' =>
{ alias =>
{ break => 'break_dance' },
exclude => 'break' };
And then ...
package FragileDancer;
use Moose;
sub break {
my $self = shift;
$self->break_dance;
if ( rand(1) < 0.5 ) {
$self->break_bone;
}
}
Still Full of Fail
- Roles are also about semantics!
- We've fulfilled the letter and lost the spirit
- Roles have a meaning
- Think twice before blindly aliasing and excluding methods!
Hot Role-on-Role Action
package Comparable;
use Moose::Role;
requires 'compare';
Hot Role-on-Role Action
package TestsEquality;
use Moose::Role;
with 'Comparable';
sub is_equal {
my $self = shift;
return $self->compare(@_) == 0;
}
And then ...
package Integer;
use Moose;
with 'TestsEquality';
# Satisfies the Comparable role
sub compare { ... }
Integer->does('TestsEquality'); # true
Integer->does('Comparable'); # also true!
Name Conflicts Between Roles
package HasSubProcess;
use Moose::Role;
sub execute { ... }
package Killer;
use Moose::Role;
with 'HasSubProcess';
sub execute { ... }
Delayed Conflict
package StateOfTexas;
with 'Killer';
StateOfTexas
must implement its own execute
- But loading the
Killer
role by itself does not cause an error
Roles as Interfaces
- Roles can
require
methods of their consumers
- Compile-time checks
- Method must exist when the role is consumed
The Attribute Gotcha
package HasSize;
use Moose::Role;
requires 'size';
package Shirt;
use Moose;
with 'HasSize';
has size => ( is => 'ro' );
The Attribute Gotcha Workaround
package HasSize;
use Moose::Role;
requires 'size';
package Shirt;
use Moose;
has size => ( is => 'ro' );
with 'HasSize';
Compile-time Is a Lie
- Really, it's package load time
- That's run-time, but before the "real" run-time
- Moose does not rewire Perl, it's just sugar!
- (but
MooseX::Declare
does rewire Perl)
Enforcing Roles
package Comparison;
use Moose;
has [ 'left', 'right' ] => (
is => 'ro',
does => 'Comparable',
);
- A sneak peek at type constraints
Roles Can Be Applied to Objects
use Moose::Util qw( apply_all_roles );
my $fragile_person = Person->new( ... );
apply_all_roles( $fragile_person, 'IsFragile' );
- Does not change the
Person
class
- Works with non-Moose classes, great for monkey-patching!
Roles Are Dirty Too
- Once again, clean up those Moose droppings
package Comparable;
use Moose::Role;
requires 'compare';
no Moose::Role;
- But roles cannot be made immutable
The Zen of Roles
- Roles represent discrete units of ...
- Roles are shareable between unrelated classes
- Roles are what a class does, not what it is
- Roles add functionality, inheritance specializes
Abstract Examples
- Human @ISA Animal
- Human does Toolmaker (as does Chimpanzee)
- Car @ISA Vehicle
- Car does HasEngine
Real Examples
- Objects representing SQL database components and queries
- Schema, Table, Column, ColumnAlias
- Select, Insert, Update, Delete
Real Examples
- Column and ColumnAlias both do ColumnLike
- ColumnLike things can be used in certain parts of queries
- All queries do HasWhereClause
- Select does Comparable and Selectable (for subselects)
- A where clause requires its components to do Comparable
Roles Summary
- Roles can define an interface with
requires
- Roles can have state (attributes) and behavior (methods)
- Roles can mix interface, state, & behavior
- Roles are composed (flattened) into classes
- Roles can do other roles
- Roles can be used as a type in APIs (must do Comparable)
Questions?
Exercises
# cd exercises
# perl bin/prove -lv t/02-roles.t
Iterate til this passes all its tests
Part 3: Basic Attributes
Attributes Are Huge
- Moose's biggest feature
- The target of many MooseX modules
Quick Review
- Declared with
has
- Read-only or read-write
package Shirt;
use Moose;
has 'color' => ( is => 'ro' );
has 'is_ripped' => ( is => 'rw' );
Required-ness
- Required means "must be passed to the constructor"
- But can be
undef
Required-ness
package Person;
use Moose;
has first_name => (
is => 'ro',
required => 1,
);
Person->new( first_name => undef ); # ok
Person->new(); # kaboom
Default and Builder
- Attributes can have defaults
- Simple non-referecne scalars (number, string)
- Subroutine reference
- A builder method
Default
- Can be a non-reference scalar (including
undef
)
package Person;
use Moose;
has bank => (
is => 'rw',
default => 'Spire FCU',
);
Default
- Can be a subroutine reference
package Person;
use Moose;
has bank => (
is => 'rw',
default =>
sub { Bank->new(
name => 'Spire FCU' ) },
);
Default as a Subroutine Reference
- Called as a method on the object
- Called anew for each object
Why No Other Reference Types?
package Person;
use Moose;
has bank => (
is => 'rw',
default => Bank->new(
name => 'Spire FCU' ),
);
- Now every person shares the same Bank object!
Defaulting to an Empty Reference
package Person;
use Moose;
has packages => (
is => 'rw',
default => sub { [] },
);
What if I Want to Share?
package Person;
use Moose;
my $highlander_bank =
Bank->new( name => 'Spire FCU' );
has bank => (
is => 'rw',
default => sub { $highlander_bank },
);
Builder
- A method name which returns the default
Builder
package Person;
use Moose;
has bank => (
is => 'rw',
builder => '_build_bank',
);
sub _build_bank {
my $self = shift;
return Bank->new( name => 'Spire FCU' );
}
Default vs Builder
- Use default for simple scalars
- Use default to return empty references
- Use default for very trivial subroutine references
- Use builder for everything else
Builder Bonuses
- Can be overridden and method modified, because it's called by name
- Roles can require a builder
Role Requires Builder
package HasBank;
use Moose::Role;
requires '_build_bank';
has bank => (
is => 'ro',
builder => '_build_bank',
);
Lazy, Good for Nothing Attributes
- Normally, defaults are generated during object construction
- This can be expensive
- We want to default to
$self->size * 2
, but attribute initialization order is unpredictable
- Use lazy attributes!
The Power of Dynamic Defaults
package Person;
use Moose;
has shoe_size => (
is => 'ro',
);
The Power of Dynamic Defaults
has shoes => (
is => 'ro',
lazy => 1,
builder => '_build_shoes',
);
sub _build_shoes {
my $self = shift;
return Shoes->new(
size => $_[0]->shoe_size );
}
Lazy is Good
- Lazy defaults are executed when the attribute is read
- Can see other object attributes
- Still need to watch out for circular laziness
Clearer and Predicate
- Attributes can have a value, including
undef
, or not
- Can clear the value with a clearer method
- Can check for the existence of a value with a predicate method
- By default, these methods are not created
Clearer and Predicate
package Person;
use Moose;
has account => (
is => 'ro',
lazy => 1,
builder => '_build_account',
clearer => '_clear_account',
predicate => 'has_account',
);
Clearer and Lazy Defaults
- Lazy defaults are good for computed attributes
- Clear the attribute when the source data changes
- Recalculated at next access
Renaming constructor arguments
- By default, constructor names = attribute names
- Use
init_arg
to change this
- Set
init_arg => undef
to make it unconstructable
Some init_arg
examples
package Person;
use Moose;
has shoe_size => (
is => 'ro',
init_arg => 'foot_size',
);
Person->new( shoe_size => 13 );
my $person =
Person->new( foot_size => 13 );
print $person->shoe_size;
Some init_arg
examples
package Person;
use Moose;
has shoes => (
is => 'ro',
init_arg => undef,
);
Person->new( shoes => Shoes->new );
Why Set init_arg => undef
?
- Use this with a lazy default for attributes-as-cache
- Compute the value as needed
- Ensure that it is always generated correctly (not set by constructor)
- Use triggers or method modifiers (coming soon) to clear the value
Attribute Inheritance
- By default, subclasses inherit attribute as-is
- Can change some attribute parameters in subclasses
- default
- builder
- required
- lazy
- others we've not yet covered
Attribute Inheritance Example
package Employee;
use Moose;
extends 'Person';
has '+first_name' => (
default => 'Joe',
);
Attribute Inheritance Warning
- An attribute is a contract about a class's API
- Don't break that contract in a subclass
- Especially important in the context of types
Changing Accessor Names
package Person;
use Moose;
has first_name => (
reader => 'first_name',
writer => 'first_name',
);
- The long-hand version of
is => 'rw'
Changing Accessor Names
package Person;
use Moose;
has first_name => (
reader => 'first_name',
writer => undef,
);
- The long-hand version of
is => 'ro'
Changing Accessor Names
package Person;
use Moose;
has first_name => (
reader => 'get_first_name',
writer => 'set_first_name,
);
Changing Accessor Names
package Person;
use Moose;
has first_name => (
is => 'rw',
writer => '_first_name',
);
ETOOMUCHTYPING
MooseX::FollowPBP
get_foo
and set_foo
MooseX::SemiAffordanceAccessor
foo
and set_foo
ETOOMUCHTYPING
package Person;
use Moose;
use MooseX::SemiAffordanceAccessor;
has first_name => (
is => 'rw',
);
- Creates
first_name
and set_first_name
Basic Attributes Summary
- Attributes can be
required
- Attributes can have a
default
or builder
- Attributes with a default or builder can be
lazy
- Attributes can have a
clearer
and/or predicate
- An attribute's constructor name can be changed with
init_arg
- A subclass can alter its parents' attributes
- Attribute accessor names can be changed
Questions?
Exercises
# cd exercises
# perl bin/prove -lv t/03-basic-attributes.t
Iterate til this passes all its tests
Part 4: Method Modifiers
What is a Method Modifier
- Apply to an existing method
- ... from a parent class, the current class, or a role
- Roles can provide modifiers that are applied at composition time
What is a Method Modifier
- "Inject" behavior
- Add behavior to generated methods (accessors, delegations)
- Provide roles which modify existing behavior
Before and After
- Simplest modifiers -
before
and after
- Guess when they run!
Uses for before
package Person;
use Moose;
before work => sub {
my $self = shift;
die 'I have no job!'
unless $self->has_title;
};
Uses for before
package Person;
use Moose;
before work => sub {
my $self = shift;
return unless $DEBUG;
warn "Called work on ", $self->full_name,
"with the arguments: [@_]\n";
};
Uses for after
- Also works for logging/debugging
- Post-X side-effects (recording audit info)
package Person;
use Moose;
after work => sub {
my $self = shift;
$self->work_count(
$self->work_count + 1 );
};
Other Uses
- Modifiers are useful for adding behavior to generated methods
Other Uses Example
has password => (
is => 'rw',
clearer => 'clear_password',
);
has hashed_password => (
is => 'ro',
builder => '_build_hashed_password',
clearer => '_clear_hashed_password',
);
after clear_password => sub {
my $self = shift;
$self->_clear_hashed_password;
};
before
and after
Limitations
- Cannot alter method parameters
- Cannot alter return value
- But can throw an exception
The around
Modifier
- The big gun
- Can alter parameters and/or return values
- Can skip calling the wrapped method entirely
The power of around
around insert => sub {
my $orig = shift;
my $self = shift;
$self->_validate_insert(@_);
my $new_user =
$self->$orig(
$self->_munge_insert(@_) );
$new_user->_assign_uri;
return $new_user;
};
Modifier Order
- Before runs order from last to first
- After runs in order from first to last
- Around runs in order from last to first
Modifier Order Illustrated
before 2
before 1
around 2
around 1
wrapped method
around 1
around 2
after 1
after 2
Modifiers in Roles
- Roles can use these modifiers
- Very powerful!
Modifiers in Roles
package IsUnreliable;
use Moose::Role;
requires 'run';
around run => sub {
my $orig = shift;
my $self = shift;
return if rand(1) < 0.5;
return $self->$orig(@_);
};
Augment and Inner
- Inverted
super
- From least- to most-specific
- Grandparent to parent to child
- Not allowed in roles
Augment and Inner
package Document;
sub xml { '<doc>' . inner() . '</doc>' }
package Report;
extends 'Document';
augment xml => { title() . inner() . summary() };
package TPSReport;
extends 'Report';
augment xml => { tps_xml() . inner() };
Augment and Inner
- When we call
$tps->xml
...
Document->xml
Report->xml
TPSReport->xml
Augment and Inner Usage
- Call
inner()
to "fill in the blank"
- Requires designing for subclassing
- Call
inner()
in the terminal class, just in case
Method Modifiers Summary
- Use
before
and after
for ...
- logging
- pre- or post-validation
- to add behavior to generated methods
- These two modifiers cannot change parameters or return values
Method Modifiers Summary
- Use
around
to ...
- alter parameters passed to the original method
- alter the return value of the original method
- not call the original method at all (or call a different method)
Method Modifiers Summary
- When using modifiers in a role, require the modified method
- Use
augment
and inner
to invert the normal subclassing flow ...
- Least- to most-specific (parents to children)
- Build in "insertability" (stick more stuff in the "middle")
- Always call
inner
in the most specific subclass to allow for future extension
Questions?
Exercises
# cd exercises
# perl bin/prove -lv t/04-method-modifiers.t
Iterate til this passes all its tests
Part 5: Types
Part 6: Advanced Attributes
Weak References
- A weak reference lets you avoid circular references
- Weak references do not increase the reference count
Circular Reference Illustrated
my $foo = {};
my $bar = { foo => $foo };
$foo->{bar} = $bar;
- Neither
$foo
nor $bar
go out of scope
(until the program exits)
Weakening Circular References
use Scalar::Util qw( weaken );
my $foo = {};
my $bar = { foo => $foo };
$foo->{bar} = $bar;
weaken $foo->{bar}
- When
$bar
goes out of scope, $foo->{bar}
becomes undef
Circular References in Attributes
package Person;
use Moose;
has name => ( is => 'ro' );
has friend => ( is => 'rw' );
my $alice = Person->new( name => 'Alice' );
my $bob = Person->new( name => 'Bob' );
$bob->friend($alice);
$alice->friend($bob);
The Fix
package Person;
use Moose;
has name => ( is => 'ro' );
has friend => ( is => 'rw', weak_ref => 1 );
my $alice = Person->new( name => 'Alice' );
my $bob = Person->new( name => 'Bob' );
$bob->friend($alice);
$alice->friend($bob);
Under the Hood
- A
weak_ref
attribute calls weaken
...
- during object construction
- when the attribute is set via a writer
Triggers
- A code reference run after an attribute is set
- Like an
after
modifier, but makes intentions clearer
Gross
after salary_level => {
my $self = shift;
return unless @_;
$self->clear_salary;
};
Use a Trigger Instead
Cleaner
has salary_level => (
is => 'rw',
trigger => sub { $_[0]->clear_salary },
);
Delegation
- Attributes can be objects
- Delegation transparently calls methods on those objects
Delegation Examples
package Person;
has lungs => (
is => 'ro',
isa => 'Lungs',
handles => [ 'inhale', 'exhale' ],
);
- Creates
$person->inhale
and ->exhale
methods
- Internally calls
$person->lungs->inhale
Why Delegation?
- Reduce the number of classes exposed
- Re-arrange class internals -
turn a method into an attribute with delegation
- Provide convenenience methods
Moose's handles
Parameter
- Accepts many arguments ...
- Array reference - list of methods to delegate as-is
- Hash reference - map of method names
- Regex - delegates all matching methods
- Role name - delegates all methods in the role
- Sub reference - does something complicated ;)
Array Reference
- Takes each method name and creates a simple delegation from the delegating class to the delegatee attribute
Hash Reference
- Mapping of names in the delegating class to the delegatee class
package Person;
use Moose;
has account => (
is => 'ro',
isa => 'BankAccount',
handles => {
receive_money => 'deposit',
give_money => 'withdraw',
},
);
Hash Reference Detailed
handles => {
receive_money => 'deposit',
give_money => 'withdraw',
},
$person->receive_money
= $person->account->deposit
$person->give_money
= $person->account->withdraw
Regex
package Person;
use Moose;
has name => (
is => 'ro',
isa => 'Name',
handles => qr/.*/,
);
- Creates a delegation for every method in the Name class
- Excludes
meta
and methods inherited from Moose::Object
Role Name
package Auditor;
use Moose::Role;
sub record_change { ... }
sub change_history { ... }
package Account;
use Moose;
has history => (
is => 'ro',
does => 'Auditor',
handles => 'Auditor',
);
Role Name Detailed
- Account gets delegate methods for each method in the
Auditor
role
- record_history
- change_history
Traits and Metaclasses
- The ultimate in customization
- Per attribute metaclasses
- Per attribute roles applied to the attribute metaclass
- Change the meta-level behavior
Traits and Metaclasses
- The default metaclass is
Moose::Meta::Attribute
- Controls accessor generation, defaults, delegation, etc.
- Adding a role to this metaclass (or replacing it) allows for infinite customization
Traits and Metaclasses
- Can add/alter/remove attribute parameter (from
has
)
- Can change behavior of created attribute
Simple Trait Example
package Person;
use Moose;
use MooseX::LabeledAttributes;
has ssn => (
traits => [ 'Labeled' ],
is => 'ro',
isa => 'Str',
label => 'Social Security Number',
);
print Person->meta
->get_attribute('ssn')->label;
Simple Metaclass Example
package Person;
use Moose;
use MooseX::LabeledAttributes;
has ssn => (
metaclass =>
'MooseX::Meta::Attribute::Labeled',
is => 'ro',
isa => 'Str',
label => 'Social Security Number',
);
print Person->meta
->get_attribute('ssn')->label;
Traits vs Metaclass
- Can apply any mix of traits to an attribute
- But just one metaclass
- Traits (aka roles) can cooperate
- Metaclasses require you to pick just one
Advanced Attributes Summary
- Use
weak_ref
to avoid circular references
- Use trigger to do an action post-attribute write
- Use delegations to hide "internal" objects
- Traits and metaclasses let you extend Moose's core attribute features
Questions?
Exercises
# cd exercises
# perl bin/prove -lv t/06-advanced-attributes.t
Iterate til this passes all its tests
Part 7: Introspection
Part 8: A Tour of MooseX
Part 9: Writing Moose Extensions