X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=moose-class%2Fslides%2Findex.html;h=70bdfc651c96000d181fa3e1664ae8a5fe5a06b6;hb=c1d605dbbaf56eed6436ec8821158c509b8210fe;hp=10d0339e76129eee80f2d3b93fd3046f9deef440;hpb=ddd87d751a8c055d95240d6af73109770c743466;p=gitmo%2Fmoose-presentations.git diff --git a/moose-class/slides/index.html b/moose-class/slides/index.html index 10d0339..70bdfc6 100644 --- a/moose-class/slides/index.html +++ b/moose-class/slides/index.html @@ -1,10 +1,10 @@ - +
package Person;
use Moose;
-has 'first_name' => ( is => 'rw' );
+has first_name => ( is => 'rw' );
before 'foo'
=> sub { warn 'About to call foo()' };
@@ -225,7 +245,10 @@ after 'foo'
my @return =
$self->$real_foo( @_, bar => 42 );
- return ( @return, 'modify return values' );
+ return (
+ @return,
+ 'modify return values'
+ );
};
package Person;
use Moose;
-has 'weight' => ( is => 'ro', isa => 'Int' );
+has weight => (
+ is => 'ro',
+ isa => 'Int',
+);
# kaboom
Person->new( weight => 'fat' );
@@ -268,14 +294,14 @@ Person->new( weight => 'fat' );
package Person;
use Moose;
-has 'blog_uri' => (
+has blog_uri => (
is => 'rw',
isa => 'URI',
- handles => { 'blog_hostname' => 'host' },
+ handles => { 'blog_host' => 'host' },
);
-$person->blog_hostname;
-# really calls $person->blog_uri->host
+$person->blog_host;
+# really calls $person->blog_uri->host
$self->{last_nane}
+ $self->{last_nane}
@_
, returns a hash reference of attribute names/value$class->SUPER::BUILDARGS(@_)
as a fallback!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')
+new
returnspackage 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;
+ }
+}
+Person->new(@_)
+
+ Person->BUILDARGS(@_)
to turn @_
into a hashref$new_object->BUILDALL($constructor_args)
+ BUILD
methodsDESTROY
, but Moose makes sure all DEMOLISH
methods in a hierarchy are calledextends
is sugar for declaring parent classesuse base
package Employee;
@@ -871,18 +990,18 @@ use Moose;
Each call to extends
resets your parents
- WRONG
+ Wrong
package EvilEmployee;
use Moose;
extends 'Person';
-extends 'Thief';
+extends 'Thief';
- package EvilEmployee;
use Moose;
-extends 'Person', 'Thief';
+extends 'Person', 'Thief';
package My::LWP;
use Moose;
-extends 'LWP';
+extends 'LWP';
Moose::Object
, so ...
@@ -905,28 +1024,30 @@ extends 'LWP';
overrides
and super
override
and super
overrides
is another method modifieroverride
is another method modifierSUPER::
overrides
and super
override
and super
package Employee;
use Moose;
-extends 'Person';
+extends 'Person';
-overrides work => sub {
+override work => sub {
my $self = shift;
- die "Pay me first" unless $self->got_paid;
+ die "Pay me first"
+ unless $self->got_paid;
super();
};
+super
has 'foo'
package Person;
use Moose;
-has 'first_name' => ( is => 'rw' );
+has first_name => ( is => 'rw' );
my $person =
Person->new( first_name => 'Dave' );
@@ -970,7 +1091,7 @@ print $person->first_name; # Stevan
package Person;
use Moose;
-has 'first_name' => ( is => 'ro' );
+has first_name => ( is => 'ro' );
my $person =
Person->new( first_name => 'Dave' );
@@ -1021,6 +1142,7 @@ Person->can('extends');
no Moose
at the end of a package is a best practiceuse namespace::autoclean
at the topuse Moose
Class->meta
Moose::Object
base classextends
, override
, and super
has
, is => 'ro'
, & is => 'rw'
no Moose
__PACKAGE__->meta->make_immutable
$ cd exercises -$ prove -lv t/00-prereq.t +++ +Exercises
-Missing anything? Install it. (see tarballs/) +# cd exercises -# prove -lv t/01-classes.t -# +# 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';
+
+- Only one
+with
!++ +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 ownexecute
- 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 ... +
++
+- state
+- behavior
+- interface
+- 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
andset_foo
- +
MooseX::SemiAffordanceAccessor
foo
andset_foo
++ +ETOOMUCHTYPING
+ ++ +package Person; +use Moose; +use MooseX::SemiAffordanceAccessor; + +has first_name => ( + is => 'rw', +);
+
+- Creates
+first_name
andset_first_name
++ +Basic Attributes Summary
+ ++
+- Attributes can be
+required
- Attributes can have a
+default
orbuilder
- Attributes with a default or builder can be
+lazy
- Attributes can have a
+clearer
and/orpredicate
- 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
andafter
- Guess when they run!
+++ +Uses for
+ +before
+
+ +- As a pre-call check
++package Person; +use Moose; + +before work => sub { + my $self = shift; + die 'I have no job!' + unless $self->has_title; +};
++ +Uses for
+ +before
+
+ +- Logging/Debugging
++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
andafter
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
andafter
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
andinner
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
+
+- Like Perl's
+if ($foo)
++ +Value (and subtypes)
+ ++
+- +
Value
is true when! ref $thing
- +
Value
andStr
are effectively the same, butStr
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/
(orStr
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 orundef
++ +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 withMooseX::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}
becomesundef
++ +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 callsweaken
... ++
+- 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 clearerGross
+ ++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 fromMoose::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
+ ++
+ +- Then call it like this:
++ +#!/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
+++ + +More Information
+ ++
+- http://moose.perl.org/
+- Moose::Manual and Moose::Cookbook
+- irc://irc.perl.org/#moose
+- mailing list - moose@perl.org
+- Slides and exercises are in Moose's git repo: +
+
+ git://jules.scsys.co.uk/gitmo/moose-presentations