Introduce Yourselves
- Your name
- What you do with Perl
- Why you're here today (optional)
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 ...
- Mutability (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_host' => 'host' },
);
$person->blog_host;
# 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;
}
}
Object Construction a la Moose
Person->new(@_)
- Calls
Person->BUILDARGS(@_)
to turn @_
into a hashref
- Blesses a reference
- Populates attributes based on the hashref from #1
- Calls
$new_object->BUILDALL($constructor_args)
... which calls all BUILD
methods
- Returns the object
The Object is Opaque
- Technically it's a hash reference
- If you ever treat it as one you are doing it wrong!
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
- Also ensures metaclass compatibility between parent and child
- Do not
use base
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
- Or
use namespace::autoclean
at the top
- 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
# perl install-moose (if needed)
# perl bin/prove -lv t/01-classes.t
# edit lib/Person.pm and lib/Employee.pm
Iterate til this passes all its tests
Part 2: Roles
Just What Is a Role?
- Mixin? Interface? Trait?
- Yes ... and more!
Roles - 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 ...
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' },
-excludes => 'break' },
'CanBreakdance' =>
{ -alias =>
{ break => 'break_it_down' },
-excludes => 'break' };
And then ...
package FragileDancer;
use Moose;
sub break {
my $self = shift;
$self->break_it_down;
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-reference 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' ) },
);
Subroutine Reference Default
- 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 Nothin' 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 => $self->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 => (
accessor => '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',
);
- Can also mix-and-match
is
and explicit names
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
More Modifier Examples
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 in 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 =>
sub { title() . inner() . summary() };
package TPSReport;
extends 'Report';
augment xml =>
sub { 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
A Type System for Perl
- Sort of ...
- Variables are not typed
- Attributes can have types
- MooseX modules let you define method signatures
Components of a Moose Type
- A type is a name and a constraint
- Types have a hierarchy
- Constraints are cumulative from parents
- Types can have associated coercions
Built-in Type Hierarchy
Any
Item
Bool
Maybe[`a]
Undef
Defined
Value
Num
Int
Str
ClassName
RoleName
Built-in Type Hierarchy
(Item)
(Defined)
(Value)
Ref
ScalarRef
ArrayRef[`a]
HashRef[`a]
CodeRef
RegexpRef
GlobRef
FileHandle
Object
Bool
True
1
924.1
'true'
{}
False
0
0.0
'0'
undef
Value (and subtypes)
Value
is true when ! ref $thing
Value
and Str
are effectively the same, but Str
is more expressive
- An overloaded object which numifies does not pass the
Num
constraint!
- Perl 5's overloading is hopelessly broken
ClassName and RoleName
- A string with a package name
- The package must already be loaded
Parameterizable Types
- What does
ArrayRef[`a]
mean?
s/`a/Int/
(or Str
or ...)
- When you use it you can write ...
ArrayRef
(== ArrayRef[Item]
)
ArrayRef[Str]
ArrayRef[MyTypeName]
ArrayRef[HashRef[Maybe[Int]]]
Maybe[`a]
- Maybe means either the named type or
undef
Maybe[Int]
accepts integers or undef
Type Union
- This or that (or that or ...)
Int | ArrayRef[Int]
- But use a coercion instead when possible
- Or use a
role_type
, duck_type
, or anything not a union
- A union is often a code smell
Making Your Own Types
use Moose::Util::TypeConstraints;
subtype 'PositiveInt',
as 'Int',
where { $_ > 0 },
message
{ "The value you provided ($_)"
. " was not a positive int." };
has size => (
is => 'ro',
isa => 'PositiveInt',
);
Automatic Types
- Moose creates a type for every Moose class and role
- Unknown names are assumed to be classes
Automatic Types
package Employee;
use Moose;
has manager => (
is => 'rw',
isa => 'Employee',
);
has start_date => (
is => 'ro',
isa => 'DateTime',
);
Subtype Shortcuts - class_type
use Moose::Util::TypeConstraints;
class_type 'DateTime';
subtype 'DateTime',
as 'Object',
where { $_->isa('DateTime') },
message { ... };
Subtype Shortcuts - role_type
use Moose::Util::TypeConstraints;
role_type 'Printable';
subtype 'Printable',
as 'Object',
where
{ Moose::Util::does_role(
$_, 'Printable' ) },
message { ... };
Subtype Shortcuts - duck_type
use Moose::Util::TypeConstraints;
duck_type Car => qw( run break_down );
subtype 'Car',
as 'Object',
where { all { $_->can($_) }
qw( run break_down ) },
message { ... };
Subtype Shortcuts - enum
use Moose::Util::TypeConstraints;
enum Color => qw( red blue green ) );
my %ok = map { $_ => 1 }
qw( red blue green );
subtype 'Color'
as 'Str',
where { $ok{$_} },
message { ... };
Anonymous Subtypes
package Person;
my $posint =
subtype as 'Int', where { $_ > 0 };
has size => (
is => 'ro',
isa => $posint,
);
- Shortcuts have anonymous forms as well
Coercions
use Moose::Util::TypeConstraints;
subtype 'UCStr',
as 'Str',
where { ! /[a-z]/ };
Coercions
coerce 'UCStr',
from 'Str',
via { uc };
has shouty_name => (
is => 'ro',
isa => 'UCStr',
coerce => 1,
);
Coercion Examples
subtype 'My::DateTime',
as class_type 'DateTime';
coerce 'My::DateTime',
from 'HashRef',
via { DateTime->new( %{$_} ) };
coerce 'My::DateTime',
from 'Int',
via { DateTime->from_epoch(
epoch => $_ ) };
- Use coercion to inflate a value
Coercion Examples
coerce 'ArrayRef[Int]',
from 'Int',
via { [ $_ ] };
- Instead of union -
Int | ArrayRef[Int]
Using Types with Attributes
package Person;
has height => (
is => 'rw',
isa => 'Num',
);
has favorite_numbers => (
is => 'rw',
isa => 'ArrayRef[Int]',
coerce => 1,
);
More Droppings
Moose::Util::TypeConstraints
also needs cleanup
package Person;
use Moose;
use Moose::Util::TypeConstraints;
subtype ...;
no Moose;
no Moose::Util::TypeConstraints;
Typed Methods (Low-tech)
package Person;
use MooseX::Params::Validate qw( validated_list );
sub work {
my $self = shift;
my ( $tasks, $can_rest ) =
validated_list(
\@_,
tasks =>
{ isa => 'ArrayRef[Task]',
coerce => 1 },
can_rest =>
{ isa => 'Bool',
default => 0 },
);
...
}
Typed Methods (High-tech)
package Person;
use MooseX::Method::Signatures;
method work ( ArrayRef[Task] :$tasks,
Bool :$can_rest = 0 ) {
my $self = shift;
...
}
Digression: The Type Registry
- Types are actually
Moose::Meta::TypeConstraints
objects
- Stored in an interpreter-global registry mapping names to objects
Danger!
- Coercions are attached to type objects
- Therefore also global
- Name conflicts between modules!
- Coercion conflicts between modules!
Namespace Fix
- Use some sort of pseudo-namespacing scheme
- Never coerce directly to a class name, or to built-in types
Namespace Fix
use Moose::Util::TypeConstraints;
subtype 'MyApp::Type::DateTime',
as 'DateTime';
coerce 'MyApp::Type::DateTime',
from 'HashRef',
via { DateTime->new( %{$_} ) }
has creation_date => (
is => 'ro',
isa => 'MyApp::Type::DateTime',
coerce => 1,
);
Namespace Fix
subtype 'MyApp::Type::ArrayOfInt',
as 'ArrayRef[Int]';
coerce 'MyApp::Type::ArrayOfInt',
from 'Int',
via { [ $_ ] };
Namespace Fix Pros and Cons
- Relatively simple
- Already built into Moose
- Conflates type and module namespaces
- Type names are strings, so typos are easy to make and may be hard to find
MooseX::Types
package MyApp::Types;
use MooseX::Types
-declare => [ qw( ArrayOfInt ) ];
use MooseX::Types::Moose
qw( ArrayRef Int );
subtype ArrayOfInt,
as ArrayRef[Int];
coerce ArrayOfInt
from Int,
via { [ $_ ] };
MooseX::Types
package MyApp::Account;
use MyApp::Types qw( ArrayOfInt );
has transaction_history => (
is => 'rw',
isa => ArrayOfInt,
);
MooseX::Types
- Type names are exported functions, catches typos early
- Types must be pre-declared
- Types are stored with namespaces internally, but externally are short
- Import existing Moose types as functions from
MooseX::Types::Moose
- Still need string names for things like
ArrayRef['Email::Address']
MooseX::Types Pros and Cons
- Catches typos at compile time
- Automatic namespacing
- One more thing to install and learn
- Every name gets types twice (declared and then defined)
- Still stuck with strings when referring to class or role names
- Coercion gotcha from earlier still applies to types exported from
MooseX::Types::Moose
Recommendation
- Use
MooseX::Types
- Compile time error catching and automatic namespacing are huge wins
- Docs from
Moose::Util::TypeConstraints
are 98% compatible with MooseX::Types
anyway
- A function exported by a type library works wherever a type name would
Questions?
Exercises
# cd exercises
# perl bin/prove -lv t/05-types.t
Iterate til this passes all its tests
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
Native Delegation
- Delegate to unblessed Perl types
- Scalar, array or hash ref, etc
- Treat Perl types as objects
- Still uses
handles
- Pretend that native Perl types have methods
Native Delegation - Array(Ref)
- Methods include:
push
shift
elements
- returns all elements
count
is_empty
- quite a few more
Native Delegation - Array(Ref)
package Person;
use Moose;
has _favorite_numbers => (
traits => [ 'Array' ],
is => 'ro',
isa => 'ArrayRef[Int]',
default => sub { [] },
init_arg => undef,
handles =>
{ favorite_numbers => 'elements',
add_favorite_number => 'push',
},
);
Native Delegation - Array(Ref)
my $person = Person->new();
$person->add_favorite_number(7);
$person->add_favorite_number(42);
print "$_\n"
for $person->favorite_numbers;
# 7
# 42
Native Delegation
- Native types are ...
- Number -
add
, mul
, ...
- String -
append
, chop
, ...
- Counter -
inc
, dec
, ...
- Bool -
set
, toggle
, ...
- Hash -
get
, set
, ...
- Array - already saw it
- Code -
execute
, that's it
Curried Delegation
- A delegation with some preset arguments
- Works with object or Native delegation
Curried Delegation
package Person;
use Moose;
has account => (
is => 'ro',
isa => 'BankAccount',
handles => {
receive_100 =>
[ 'deposit', 100 ]
give_100 =>
[ 'withdraw', 100 ]
},
);
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 Brief Tour of MooseX
Notable Moose Extensions on CPAN
- Not comprehensive
- 128 MooseX distributions on CPAN as of 09/24/2009
- Some of them are crap
Already Mentioned Several
- MooseX::NonMoose - best solution for subclassing non-Moose parents
- MooseX::Declare - real Perl 5 OO
- MooseX::FollowPBP and MooseX::SemiAffordanceAccessor
- MooseX::Params::Validate and MooseX::Method::Signatures
- MooseX::Types
MooseX::Declare
use MooseX::Declare;
use 5.10.0; # for say
class Person {
has greeting
=> ( is => 'ro', isa => 'Str' );
method speak {
say $self->greeting;
}
}
MooseX::Declare
- Still experimental-ish, but seeing more and more use
- Not a source filter!
- Hooks into the Perl parser rather than filtering all your code
MooseX::StrictConstructor
- By default, unknown constructor arguments are ignore
- MX::StrictConstructor turns these into an error
MooseX::StrictConstructor
package Person;
use Moose;
use MooseX::StrictConstructor;
has name => ( is => 'ro' );
Person->new
( nane => 'Ringo Shiina' ); # kaboom
MooseX::Traits
- Combines object construction and role application
- Makes it easy to create one-off customized objects
MooseX::Traits
package MyApp::Thingy;
use Moose;
with 'MooseX::Traits';
my $thing =
MyApp::Thingy->new_with_traits
( traits => [ 'Foo', 'Bar' ],
size => 42 );
MooseX::Getopt
- Makes command-line interface programs easy!
- Construct an object from CLI arguments
MooseX::Getopt
package App::CLI;
use Moose;
with 'MooseX::Getopt';
has file =>
( is => 'ro', required => 1 );
has filters =>
( is => 'ro', isa => 'Str' );
sub run { ... }
MooseX::Getopt
#!/usr/bin/perl
use App::CLI;
App::CLI->new_with_options()->run();
$ myapp-cli \
--file foo \
--filters compress \
--filters sanitize
MooseX::Clone
package Person;
use Moose;
with 'MooseX::Clone';
my $person = Person->new;
my $clone = $person->clone;
MooseX::NonMoose
- Highly recommended for subclassing non-Moose parents
- Gets all the little annoying details right
MooseX::Role::Parameterized
package HasCollection;
use MooseX::Role::Parameterized;
parameter type => ( isa => 'Str',
default => 'Item' );
role {
my $p = shift;
my $type = 'ArrayRef[' . $p->type() . ']';
has collection =>
( is => 'ro',
isa => $type );
};
MooseX::Role::Parameterized
package Person;
use Moose;
with HasCollection => { type => 'Int' };
Questions?
Part 9: Writing Moose Extensions
The End