From: Dave Rolsky Date: Sun, 3 May 2009 18:27:07 +0000 (-0500) Subject: Moved recipes to get rid of Basic 8 as a TODO, and just let them be numbered sequenti... X-Git-Tag: 0.78~38 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=1f476b5f71cc377deee9c2bb1f25d57c71e857e1;p=gitmo%2FMoose.git Moved recipes to get rid of Basic 8 as a TODO, and just let them be numbered sequentially. --- diff --git a/lib/Moose/Cookbook.pod b/lib/Moose/Cookbook.pod index 6163c85..67ccc15 100644 --- a/lib/Moose/Cookbook.pod +++ b/lib/Moose/Cookbook.pod @@ -58,28 +58,22 @@ the usual method overriding style "inside-out". Making a class immutable greatly increases the speed of accessors and object construction. -=item L - Managing complex relations with trigger (TODO) - -I - -Work off of this http://code2.0beta.co.uk/moose/svn/Moose/trunk/t/200_examples/007_Child_Parent_attr_inherit.t - -=item L - Builder methods and lazy_build +=item L - Builder methods and lazy_build The builder feature provides an inheritable and role-composable way to provide a default attribute value. -=item L - Operator overloading, subtypes, and coercion +=item L - Operator overloading, subtypes, and coercion Demonstrates using operator overloading, coercion, and subtypes to model how eye color is determined during reproduction. -=item L - Using BUILDARGS and BUILD to hook into object construction +=item L - Using BUILDARGS and BUILD to hook into object construction This recipe demonstrates the use of C and C to hook into object construction. -=item L - Extending a non-Moose base class +=item L - Extending a non-Moose base class In this recipe, we make a Moose-based subclass of L, a module which does not use Moose itself. diff --git a/lib/Moose/Cookbook/Basics/Recipe10.pod b/lib/Moose/Cookbook/Basics/Recipe10.pod index 8cafe18..ecbfc99 100644 --- a/lib/Moose/Cookbook/Basics/Recipe10.pod +++ b/lib/Moose/Cookbook/Basics/Recipe10.pod @@ -3,267 +3,127 @@ =head1 NAME -Moose::Cookbook::Basics::Recipe10 - Operator overloading, subtypes, and coercion +Moose::Cookbook::Basics::Recipe11 - Using BUILDARGS and BUILD to hook into object construction =head1 SYNOPSIS - package Human; + package Person; - use Moose; - use Moose::Util::TypeConstraints; - - subtype 'Gender' - => as 'Str' - => where { $_ =~ m{^[mf]$}s }; - - has 'gender' => ( is => 'ro', isa => 'Gender', required => 1 ); - - has 'mother' => ( is => 'ro', isa => 'Human' ); - has 'father' => ( is => 'ro', isa => 'Human' ); - - use overload '+' => \&_overload_add, fallback => 1; + has 'ssn' => ( + is => 'ro', + isa => 'Str', + predicate => 'has_ssn', + ); - sub _overload_add { - my ( $one, $two ) = @_; + has 'country_of_residence' => ( + is => 'ro', + isa => 'Str', + default => 'usa' + ); - die('Only male and female humans may create children') - if ( $one->gender() eq $two->gender() ); + has 'first_name' => ( + is => 'ro', + isa => 'Str', + ); - my ( $mother, $father ) - = ( $one->gender eq 'f' ? ( $one, $two ) : ( $two, $one ) ); + has 'last_name' => ( + is => 'ro', + isa => 'Str', + ); - my $gender = 'f'; - $gender = 'm' if ( rand() >= 0.5 ); + sub BUILDARGS { + my $class = shift; - return Human->new( - gender => $gender, - mother => $mother, - father => $father, - ); + if ( @_ == 1 && ! ref $_[0] ) { + return { ssn => $_[0] }; + } + else { + return $class->SUPER::BUILDARGS(@_); + } } -=head1 DESCRIPTION - -This Moose cookbook recipe shows how operator overloading, coercion, -and sub types can be used to mimic the human reproductive system -(well, the selection of genes at least). - -=head1 INTRODUCTION - -Our C class uses operator overloading to allow us to "add" two -humans together and produce a child. Our implementation does require -that the two objects be of opposite genders. Remember, we're talking -about biological reproduction, not marriage. - -While this example works as-is, we can take it a lot further by adding -genes into the mix. We'll add the two genes that control eye color, -and use overloading to combine the genes from the parent to model the -biology. - -=head2 What is Operator Overloading? - -Overloading is I a Moose-specific feature. It's a general OO -concept that is implemented in Perl with the C -pragma. Overloading lets objects do something sane when used with -Perl's built in operators, like addition (C<+>) or when used as a -string. - -In this example we overload addition so we can write code like -C<$child = $mother + $father>. - -=head1 GENES - -There are many genes which affect eye color, but there are two which -are most important, I and I. We will start by making a -class for each gene. - -=head2 Human::Gene::bey2 - - package Human::Gene::bey2; - - use Moose; - use Moose::Util::TypeConstraints; - - type 'bey2_color' => where { $_ =~ m{^(?:brown|blue)$} }; - - has 'color' => ( is => 'ro', isa => 'bey2_color' ); - -This class is trivial, We have a type constraint for the allowed -colors, and a C attribute. - -=head2 Human::Gene::gey - - package Human::Gene::gey; - - use Moose; - use Moose::Util::TypeConstraints; - - type 'gey_color' => where { $_ =~ m{^(?:green|blue)$} }; - - has 'color' => ( is => 'ro', isa => 'gey_color' ); - -This is nearly identical to the C class, except -that the I gene allows for different colors. - -=head1 EYE COLOR - -We could just give add four attributes (two of each gene) to the -C class, but this is a bit messy. Instead, we'll abstract the -genes into a container class, C. Then a C can -have a single C attribute. - - package Human::EyeColor; + sub BUILD { + my $self = shift; - use Moose; - use Moose::Util::TypeConstraints; - - coerce 'Human::Gene::bey2' - => from 'Str' - => via { Human::Gene::bey2->new( color => $_ ) }; - - coerce 'Human::Gene::gey' - => from 'Str' - => via { Human::Gene::gey->new( color => $_ ) }; - - has [qw( bey2_1 bey2_2 )] => - ( is => 'ro', isa => 'Human::Gene::bey2', coerce => 1 ); - - has [qw( gey_1 gey_2 )] => - ( is => 'ro', isa => 'Human::Gene::gey', coerce => 1 ); - -The eye color class has two of each type of gene. We've also created a -coercion for each class that coerces a string into a new object. Note -that a coercion will fail if it attempts to coerce a string like -"indigo", because that is not a valid color for either type of gene. - -As an aside, you can see that we can define several identical -attributes at once by supply an array reference of names as the first -argument to C. - -We also need a method to calculate the actual eye color that results -from a set of genes. The I brown gene is dominant over both blue -and green. The I green gene dominant over blue. - - sub color { - my ($self) = @_; - - return 'brown' - if ( $self->bey2_1->color() eq 'brown' - or $self->bey2_2->color() eq 'brown' ); - - return 'green' - if ( $self->gey_1->color() eq 'green' - or $self->gey_2->color() eq 'green' ); - - return 'blue'; + if ( $self->country_of_residence eq 'usa' ) { + die 'Cannot create a Person who lives in the USA without an ssn.' + unless $self->has_ssn; + } } -We'd like to be able to treat a C object as a string, -so we define a string overloading for the class: - - use overload '""' => \&color, fallback => 1; - -Finally, we need to define overloading for addition. That way we can -add together to C objects and get a new one with a -new (genetically correct) eye color. +=head1 DESCRIPTION - use overload '+' => \&_overload_add, fallback => 1; +This recipe demonstrates the use of C and C. By +defining these methods, we can hook into the object construction +process without overriding C. - sub _overload_add { - my ( $one, $two ) = @_; +The C method is called I an object has been +created. It is called as a class method, and receives all of the +parameters passed to the C method. It is expected to do something +with these arguments and return a hash reference. The keys of the hash +must be attribute Cs. - my $one_bey2 = 'bey2_' . _rand2(); - my $two_bey2 = 'bey2_' . _rand2(); +The primary purpose of C is to allow a class to accept +something other than named arguments. In the case of our C +class, we are allowing it to be called with a single argument, a +social security number: - my $one_gey = 'gey_' . _rand2(); - my $two_gey = 'gey_' . _rand2(); + my $person = Person->new('123-45-6789'); - return Human::EyeColor->new( - bey2_1 => $one->$one_bey2->color(), - bey2_2 => $two->$two_bey2->color(), - gey_1 => $one->$one_gey->color(), - gey_2 => $two->$two_gey->color(), - ); - } +The key part of our C is this conditional: - sub _rand2 { - return 1 + int( rand(2) ); - } + if ( @_ == 1 && ! ref $_[0] ) { + return { ssn => $_[0] }; + } -When two eye color objects are added together the C<_overload_add()> -method will be passed two C objects. These are the -left and right side operands for the C<+> operator. This method -returns a new C object. +By default, Moose constructors accept a list of key-value pairs, or a +hash reference. We need to make sure that C<$_[0]> is not a reference +before assuming it is a social security number. -=head1 ADDING EYE COLOR TO Cs +We call C<< $class->SUPER::BUILDARGS(@_) >> to handle all the other +cases. You should always do this in your own C methods, +since L provides its own C method that +handles hash references and a list of key-value pairs. -Our original C class requires just a few changes to incorporate -our new C class. +The C method is called I the object is constructed, but +before it is returned to the caller. The C method provides an +opportunity to check the object state as a whole. This is a good place +to put logic that cannot be expressed as a type constraint on a single +attribute. - use List::MoreUtils qw( zip ); +In the C class, we need to check the relationship between two +attributes, C and C. We throw an exception +if the object is not logically consistent. - coerce 'Human::EyeColor' - => from 'ArrayRef' - => via { my @genes = qw( bey2_1 bey2_2 gey_1 gey_2 ); - return Human::EyeColor->new( zip( @genes, @{$_} ) ); }; +=head1 MORE CONSIDERATIONS - has 'eye_color' => ( - is => 'ro', - isa => 'Human::EyeColor', - coerce => 1, - required => 1, - ); - -We also need to modify C<_overload_add()> in the C class to -account for eye color: - - return Human->new( - gender => $gender, - eye_color => ( $one->eye_color() + $two->eye_color() ), - mother => $mother, - father => $father, - ); +This recipe is made significantly simpler because all of the +attributes are read-only. If the C attribute +were settable, we would need to check that a Person had an C if +the new country was C. This could be done with a C +modifier. =head1 CONCLUSION -The three techniques we used, overloading, subtypes, and coercion, -combine to provide a powerful interface. - -If you'd like to learn more about overloading, please read the -documentation for the L pragma. - -To see all the code we created together, take a look at -F. - -=head1 NEXT STEPS - -Has this been a real project we'd probably want to: - -=over 4 +We have repeatedly discouraged overriding C in Moose +classes. This recipe shows how you can use C and C +to hook into object construction without overriding C -=item Better Randomization with Crypt::Random +The C method lets us expand on Moose's built-in parameter +handling for constructors. The C method lets us implement +logical constraints across the whole object after it is created. -=item Characteristic Base Class - -=item Mutating Genes - -=item More Characteristics - -=item Artificial Life - -=back - -=head1 AUTHORS - -Aran Clary Deltac +=head1 AUTHOR Dave Rolsky Eautarch@urth.orgE -=head1 LICENSE +=head1 COPYRIGHT AND LICENSE -This work is licensed under a Creative Commons Attribution 3.0 Unported License. +Copyright 2006-2009 by Infinity Interactive, Inc. -License details are at: L +L -=cut +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. +=cut diff --git a/lib/Moose/Cookbook/Basics/Recipe11.pod b/lib/Moose/Cookbook/Basics/Recipe11.pod index ecbfc99..1a44f67 100644 --- a/lib/Moose/Cookbook/Basics/Recipe11.pod +++ b/lib/Moose/Cookbook/Basics/Recipe11.pod @@ -1,117 +1,116 @@ =pod +=begin testing-SETUP + +BEGIN { + eval 'use DateTime; use DateTime::Calendar::Mayan;'; + if ($@) { + diag 'DateTime & DateTime::Calendar::Mayan required for this test'; + ok(1); + exit 0; + } +} + +=end testing-SETUP + =head1 NAME -Moose::Cookbook::Basics::Recipe11 - Using BUILDARGS and BUILD to hook into object construction +Moose::Cookbook::Basics::Recipe12 - Extending a non-Moose base class =head1 SYNOPSIS - package Person; + package My::DateTime; - has 'ssn' => ( - is => 'ro', - isa => 'Str', - predicate => 'has_ssn', - ); + use Moose; + extends qw( DateTime Moose::Object ); - has 'country_of_residence' => ( - is => 'ro', - isa => 'Str', - default => 'usa' - ); - - has 'first_name' => ( - is => 'ro', - isa => 'Str', - ); + use DateTime::Calendar::Mayan; - has 'last_name' => ( - is => 'ro', - isa => 'Str', + has 'mayan_date' => ( + is => 'ro', + isa => 'DateTime::Calendar::Mayan', + init_arg => undef, + lazy => 1, + builder => '_build_mayan_date', + clearer => '_clear_mayan_date', + predicate => 'has_mayan_date', ); - sub BUILDARGS { + sub new { my $class = shift; - if ( @_ == 1 && ! ref $_[0] ) { - return { ssn => $_[0] }; - } - else { - return $class->SUPER::BUILDARGS(@_); - } + my $obj = $class->SUPER::new(@_); + + return $class->meta->new_object( + __INSTANCE__ => $obj, + @_, + ); } - sub BUILD { - my $self = shift; + after 'set' => sub { + $_[0]->_clear_mayan_date; + }; - if ( $self->country_of_residence eq 'usa' ) { - die 'Cannot create a Person who lives in the USA without an ssn.' - unless $self->has_ssn; - } + sub _build_mayan_date { + DateTime::Calendar::Mayan->from_object( object => $_[0] ); } =head1 DESCRIPTION -This recipe demonstrates the use of C and C. By -defining these methods, we can hook into the object construction -process without overriding C. +This recipe demonstrates how to use Moose to subclass a parent which +is not Moose based. This recipe only works if the parent class uses a +blessed hash reference for object instances. If your parent is doing +something funkier, you should check out L. -The C method is called I an object has been -created. It is called as a class method, and receives all of the -parameters passed to the C method. It is expected to do something -with these arguments and return a hash reference. The keys of the hash -must be attribute Cs. +You might also want to check out L, which does all +the grunt work for you. -The primary purpose of C is to allow a class to accept -something other than named arguments. In the case of our C -class, we are allowing it to be called with a single argument, a -social security number: +There are a couple pieces worth noting: - my $person = Person->new('123-45-6789'); + use Moose; + extends qw( DateTime Moose::Object ); -The key part of our C is this conditional: +First, we C just like we always do. This lets us declare +attributes and use all the Moose sugar to which we are accustomed. - if ( @_ == 1 && ! ref $_[0] ) { - return { ssn => $_[0] }; - } +The C declaration explicitly include L as well +as L. This lets us use methods which are provided by +L, like C. -By default, Moose constructors accept a list of key-value pairs, or a -hash reference. We need to make sure that C<$_[0]> is not a reference -before assuming it is a social security number. +The constructor demonstrates a particular hack/pattern (hacktern?) for +working with non-Moose parent classes: -We call C<< $class->SUPER::BUILDARGS(@_) >> to handle all the other -cases. You should always do this in your own C methods, -since L provides its own C method that -handles hash references and a list of key-value pairs. + sub new { + my $class = shift; -The C method is called I the object is constructed, but -before it is returned to the caller. The C method provides an -opportunity to check the object state as a whole. This is a good place -to put logic that cannot be expressed as a type constraint on a single -attribute. + my $obj = $class->SUPER::new(@_); -In the C class, we need to check the relationship between two -attributes, C and C. We throw an exception -if the object is not logically consistent. + return $class->meta->new_object( + __INSTANCE__ => $obj, + @_, + ); + } -=head1 MORE CONSIDERATIONS +We explicitly call C<< $class->meta->new_object >> and pass the +already-created object in the C<__INSTANCE__> key. Internally, Moose +will take the existing object and initialize any attributes defined in +our subclass. -This recipe is made significantly simpler because all of the -attributes are read-only. If the C attribute -were settable, we would need to check that a Person had an C if -the new country was C. This could be done with a C -modifier. +The C modifier works just like we'd expect. The fact that +C is defined in our non-Moose parent does not matter. =head1 CONCLUSION -We have repeatedly discouraged overriding C in Moose -classes. This recipe shows how you can use C and C -to hook into object construction without overriding C +Moose can play nice with non-Moose classes when you follow the pattern +shown here. Your subclass has access to all the power of Moose, +including attribute declaration, method modifiers, type constraints +(for new attributes), and roles. -The C method lets us expand on Moose's built-in parameter -handling for constructors. The C method lets us implement -logical constraints across the whole object after it is created. +However, you won't be able to easily override a parent's "attributes", +since they're not Moose attributes. Nor will you be able to inline a +constructor, since you need to explicitly use the metaclass's object +constructor. =head1 AUTHOR @@ -119,11 +118,24 @@ Dave Rolsky Eautarch@urth.orgE =head1 COPYRIGHT AND LICENSE -Copyright 2006-2009 by Infinity Interactive, Inc. +Copyright 2009 by Infinity Interactive, Inc. L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. +=begin testing + +my $dt = My::DateTime->new( year => 1970, month => 2, day => 24 ); + +can_ok( $dt, 'mayan_date' ); +isa_ok( $dt->mayan_date, 'DateTime::Calendar::Mayan' ); +is( $dt->mayan_date->date, '12.17.16.9.19', 'got expected mayan date' ); + +$dt->set( year => 2009 ); +ok( ! $dt->has_mayan_date, 'mayan_date is cleared after call to ->set' ); + +=end testing + =cut diff --git a/lib/Moose/Cookbook/Basics/Recipe12.pod b/lib/Moose/Cookbook/Basics/Recipe12.pod deleted file mode 100644 index 1a44f67..0000000 --- a/lib/Moose/Cookbook/Basics/Recipe12.pod +++ /dev/null @@ -1,141 +0,0 @@ - -=pod - -=begin testing-SETUP - -BEGIN { - eval 'use DateTime; use DateTime::Calendar::Mayan;'; - if ($@) { - diag 'DateTime & DateTime::Calendar::Mayan required for this test'; - ok(1); - exit 0; - } -} - -=end testing-SETUP - -=head1 NAME - -Moose::Cookbook::Basics::Recipe12 - Extending a non-Moose base class - -=head1 SYNOPSIS - - package My::DateTime; - - use Moose; - extends qw( DateTime Moose::Object ); - - use DateTime::Calendar::Mayan; - - has 'mayan_date' => ( - is => 'ro', - isa => 'DateTime::Calendar::Mayan', - init_arg => undef, - lazy => 1, - builder => '_build_mayan_date', - clearer => '_clear_mayan_date', - predicate => 'has_mayan_date', - ); - - sub new { - my $class = shift; - - my $obj = $class->SUPER::new(@_); - - return $class->meta->new_object( - __INSTANCE__ => $obj, - @_, - ); - } - - after 'set' => sub { - $_[0]->_clear_mayan_date; - }; - - sub _build_mayan_date { - DateTime::Calendar::Mayan->from_object( object => $_[0] ); - } - -=head1 DESCRIPTION - -This recipe demonstrates how to use Moose to subclass a parent which -is not Moose based. This recipe only works if the parent class uses a -blessed hash reference for object instances. If your parent is doing -something funkier, you should check out L. - -You might also want to check out L, which does all -the grunt work for you. - -There are a couple pieces worth noting: - - use Moose; - extends qw( DateTime Moose::Object ); - -First, we C just like we always do. This lets us declare -attributes and use all the Moose sugar to which we are accustomed. - -The C declaration explicitly include L as well -as L. This lets us use methods which are provided by -L, like C. - -The constructor demonstrates a particular hack/pattern (hacktern?) for -working with non-Moose parent classes: - - sub new { - my $class = shift; - - my $obj = $class->SUPER::new(@_); - - return $class->meta->new_object( - __INSTANCE__ => $obj, - @_, - ); - } - -We explicitly call C<< $class->meta->new_object >> and pass the -already-created object in the C<__INSTANCE__> key. Internally, Moose -will take the existing object and initialize any attributes defined in -our subclass. - -The C modifier works just like we'd expect. The fact that -C is defined in our non-Moose parent does not matter. - -=head1 CONCLUSION - -Moose can play nice with non-Moose classes when you follow the pattern -shown here. Your subclass has access to all the power of Moose, -including attribute declaration, method modifiers, type constraints -(for new attributes), and roles. - -However, you won't be able to easily override a parent's "attributes", -since they're not Moose attributes. Nor will you be able to inline a -constructor, since you need to explicitly use the metaclass's object -constructor. - -=head1 AUTHOR - -Dave Rolsky Eautarch@urth.orgE - -=head1 COPYRIGHT AND LICENSE - -Copyright 2009 by Infinity Interactive, Inc. - -L - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=begin testing - -my $dt = My::DateTime->new( year => 1970, month => 2, day => 24 ); - -can_ok( $dt, 'mayan_date' ); -isa_ok( $dt->mayan_date, 'DateTime::Calendar::Mayan' ); -is( $dt->mayan_date->date, '12.17.16.9.19', 'got expected mayan date' ); - -$dt->set( year => 2009 ); -ok( ! $dt->has_mayan_date, 'mayan_date is cleared after call to ->set' ); - -=end testing - -=cut diff --git a/lib/Moose/Cookbook/Basics/Recipe8.pod b/lib/Moose/Cookbook/Basics/Recipe8.pod new file mode 100644 index 0000000..fd82341 --- /dev/null +++ b/lib/Moose/Cookbook/Basics/Recipe8.pod @@ -0,0 +1,125 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Basics::Recipe9 - Builder methods and lazy_build + +=head1 SYNOPSIS + + package BinaryTree; + use Moose; + + has 'node' => (is => 'rw', isa => 'Any'); + + has 'parent' => ( + is => 'rw', + isa => 'BinaryTree', + predicate => 'has_parent', + weak_ref => 1, + ); + + has 'left' => ( + is => 'rw', + isa => 'BinaryTree', + predicate => 'has_left', + lazy => 1, + builder => '_build_child_tree', + ); + + has 'right' => ( + is => 'rw', + isa => 'BinaryTree', + predicate => 'has_right', + lazy => 1, + builder => '_build_child_tree', + ); + + before 'right', 'left' => sub { + my ($self, $tree) = @_; + $tree->parent($self) if defined $tree; + }; + + sub _build_child_tree { + my $self = shift; + + return BinaryTree->new( parent => $self ); + } + +=head1 DESCRIPTION + +If you've already read L, then this +example should look very familiar. In fact, all we've done here is +replace the attribute's C parameter with a C. + +In this particular case, the C and C options act in +exactly the same way. When the C or C attribute is read, +Moose calls the builder method to initialize the attribute. + +Note that Moose calls the builder method I. Here's an example: + + my $tree = BinaryTree->new(); + + my $left = $tree->left(); + +When C<< $tree->left() >> is called, Moose calls C<< +$tree->_build_child_tree() >> in order to populate the C +attribute. If we had passed C to the original constructor, the +builder would not be called. + +There are some differences between C and C. Notably, +a builder is subclassable, and can be composed from a role. See +L for more details. + +=head2 The lazy_build shortcut + +The C attribute option can be used as sugar to specify +a whole set of attribute options at once: + + has 'animal' => ( + is => 'ro', + isa => 'Animal', + lazy_build => 1, + ); + +This is a shorthand for: + + has 'animal' => ( + is => 'ro', + isa => 'Animal', + required => 1, + lazy => 1, + builder => '_build_animal', + predicate => 'has_animal', + clearer => 'clear_animal', + ); + +If your attribute starts with an underscore, Moose is smart and will +do the right thing with the C and C, making them +both start with an underscore. The C method I starts +with an underscore. + +You can read more about C in L + +=head1 CONCLUSION + +The C option is a more OO-friendly version of the C +functionality. It also separates the default-generating code into a +well-defined method. Sprinkling your attribute definitions with +anonymous subroutines can be quite ugly and hard to follow. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2009 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Cookbook/Basics/Recipe9.pod b/lib/Moose/Cookbook/Basics/Recipe9.pod index fd82341..8cafe18 100644 --- a/lib/Moose/Cookbook/Basics/Recipe9.pod +++ b/lib/Moose/Cookbook/Basics/Recipe9.pod @@ -3,123 +3,267 @@ =head1 NAME -Moose::Cookbook::Basics::Recipe9 - Builder methods and lazy_build +Moose::Cookbook::Basics::Recipe10 - Operator overloading, subtypes, and coercion =head1 SYNOPSIS - package BinaryTree; + package Human; + use Moose; + use Moose::Util::TypeConstraints; - has 'node' => (is => 'rw', isa => 'Any'); + subtype 'Gender' + => as 'Str' + => where { $_ =~ m{^[mf]$}s }; - has 'parent' => ( - is => 'rw', - isa => 'BinaryTree', - predicate => 'has_parent', - weak_ref => 1, - ); + has 'gender' => ( is => 'ro', isa => 'Gender', required => 1 ); - has 'left' => ( - is => 'rw', - isa => 'BinaryTree', - predicate => 'has_left', - lazy => 1, - builder => '_build_child_tree', - ); + has 'mother' => ( is => 'ro', isa => 'Human' ); + has 'father' => ( is => 'ro', isa => 'Human' ); - has 'right' => ( - is => 'rw', - isa => 'BinaryTree', - predicate => 'has_right', - lazy => 1, - builder => '_build_child_tree', - ); + use overload '+' => \&_overload_add, fallback => 1; + + sub _overload_add { + my ( $one, $two ) = @_; - before 'right', 'left' => sub { - my ($self, $tree) = @_; - $tree->parent($self) if defined $tree; - }; + die('Only male and female humans may create children') + if ( $one->gender() eq $two->gender() ); - sub _build_child_tree { - my $self = shift; + my ( $mother, $father ) + = ( $one->gender eq 'f' ? ( $one, $two ) : ( $two, $one ) ); - return BinaryTree->new( parent => $self ); + my $gender = 'f'; + $gender = 'm' if ( rand() >= 0.5 ); + + return Human->new( + gender => $gender, + mother => $mother, + father => $father, + ); } =head1 DESCRIPTION -If you've already read L, then this -example should look very familiar. In fact, all we've done here is -replace the attribute's C parameter with a C. +This Moose cookbook recipe shows how operator overloading, coercion, +and sub types can be used to mimic the human reproductive system +(well, the selection of genes at least). -In this particular case, the C and C options act in -exactly the same way. When the C or C attribute is read, -Moose calls the builder method to initialize the attribute. +=head1 INTRODUCTION -Note that Moose calls the builder method I. Here's an example: +Our C class uses operator overloading to allow us to "add" two +humans together and produce a child. Our implementation does require +that the two objects be of opposite genders. Remember, we're talking +about biological reproduction, not marriage. - my $tree = BinaryTree->new(); +While this example works as-is, we can take it a lot further by adding +genes into the mix. We'll add the two genes that control eye color, +and use overloading to combine the genes from the parent to model the +biology. - my $left = $tree->left(); +=head2 What is Operator Overloading? -When C<< $tree->left() >> is called, Moose calls C<< -$tree->_build_child_tree() >> in order to populate the C -attribute. If we had passed C to the original constructor, the -builder would not be called. +Overloading is I a Moose-specific feature. It's a general OO +concept that is implemented in Perl with the C +pragma. Overloading lets objects do something sane when used with +Perl's built in operators, like addition (C<+>) or when used as a +string. -There are some differences between C and C. Notably, -a builder is subclassable, and can be composed from a role. See -L for more details. +In this example we overload addition so we can write code like +C<$child = $mother + $father>. -=head2 The lazy_build shortcut +=head1 GENES -The C attribute option can be used as sugar to specify -a whole set of attribute options at once: +There are many genes which affect eye color, but there are two which +are most important, I and I. We will start by making a +class for each gene. - has 'animal' => ( - is => 'ro', - isa => 'Animal', - lazy_build => 1, - ); +=head2 Human::Gene::bey2 + + package Human::Gene::bey2; + + use Moose; + use Moose::Util::TypeConstraints; + + type 'bey2_color' => where { $_ =~ m{^(?:brown|blue)$} }; + + has 'color' => ( is => 'ro', isa => 'bey2_color' ); + +This class is trivial, We have a type constraint for the allowed +colors, and a C attribute. + +=head2 Human::Gene::gey + + package Human::Gene::gey; + + use Moose; + use Moose::Util::TypeConstraints; + + type 'gey_color' => where { $_ =~ m{^(?:green|blue)$} }; + + has 'color' => ( is => 'ro', isa => 'gey_color' ); + +This is nearly identical to the C class, except +that the I gene allows for different colors. + +=head1 EYE COLOR + +We could just give add four attributes (two of each gene) to the +C class, but this is a bit messy. Instead, we'll abstract the +genes into a container class, C. Then a C can +have a single C attribute. + + package Human::EyeColor; + + use Moose; + use Moose::Util::TypeConstraints; + + coerce 'Human::Gene::bey2' + => from 'Str' + => via { Human::Gene::bey2->new( color => $_ ) }; + + coerce 'Human::Gene::gey' + => from 'Str' + => via { Human::Gene::gey->new( color => $_ ) }; + + has [qw( bey2_1 bey2_2 )] => + ( is => 'ro', isa => 'Human::Gene::bey2', coerce => 1 ); + + has [qw( gey_1 gey_2 )] => + ( is => 'ro', isa => 'Human::Gene::gey', coerce => 1 ); + +The eye color class has two of each type of gene. We've also created a +coercion for each class that coerces a string into a new object. Note +that a coercion will fail if it attempts to coerce a string like +"indigo", because that is not a valid color for either type of gene. + +As an aside, you can see that we can define several identical +attributes at once by supply an array reference of names as the first +argument to C. + +We also need a method to calculate the actual eye color that results +from a set of genes. The I brown gene is dominant over both blue +and green. The I green gene dominant over blue. + + sub color { + my ($self) = @_; + + return 'brown' + if ( $self->bey2_1->color() eq 'brown' + or $self->bey2_2->color() eq 'brown' ); -This is a shorthand for: + return 'green' + if ( $self->gey_1->color() eq 'green' + or $self->gey_2->color() eq 'green' ); - has 'animal' => ( - is => 'ro', - isa => 'Animal', - required => 1, - lazy => 1, - builder => '_build_animal', - predicate => 'has_animal', - clearer => 'clear_animal', + return 'blue'; + } + +We'd like to be able to treat a C object as a string, +so we define a string overloading for the class: + + use overload '""' => \&color, fallback => 1; + +Finally, we need to define overloading for addition. That way we can +add together to C objects and get a new one with a +new (genetically correct) eye color. + + use overload '+' => \&_overload_add, fallback => 1; + + sub _overload_add { + my ( $one, $two ) = @_; + + my $one_bey2 = 'bey2_' . _rand2(); + my $two_bey2 = 'bey2_' . _rand2(); + + my $one_gey = 'gey_' . _rand2(); + my $two_gey = 'gey_' . _rand2(); + + return Human::EyeColor->new( + bey2_1 => $one->$one_bey2->color(), + bey2_2 => $two->$two_bey2->color(), + gey_1 => $one->$one_gey->color(), + gey_2 => $two->$two_gey->color(), + ); + } + + sub _rand2 { + return 1 + int( rand(2) ); + } + +When two eye color objects are added together the C<_overload_add()> +method will be passed two C objects. These are the +left and right side operands for the C<+> operator. This method +returns a new C object. + +=head1 ADDING EYE COLOR TO Cs + +Our original C class requires just a few changes to incorporate +our new C class. + + use List::MoreUtils qw( zip ); + + coerce 'Human::EyeColor' + => from 'ArrayRef' + => via { my @genes = qw( bey2_1 bey2_2 gey_1 gey_2 ); + return Human::EyeColor->new( zip( @genes, @{$_} ) ); }; + + has 'eye_color' => ( + is => 'ro', + isa => 'Human::EyeColor', + coerce => 1, + required => 1, ); -If your attribute starts with an underscore, Moose is smart and will -do the right thing with the C and C, making them -both start with an underscore. The C method I starts -with an underscore. +We also need to modify C<_overload_add()> in the C class to +account for eye color: -You can read more about C in L + return Human->new( + gender => $gender, + eye_color => ( $one->eye_color() + $two->eye_color() ), + mother => $mother, + father => $father, + ); =head1 CONCLUSION -The C option is a more OO-friendly version of the C -functionality. It also separates the default-generating code into a -well-defined method. Sprinkling your attribute definitions with -anonymous subroutines can be quite ugly and hard to follow. +The three techniques we used, overloading, subtypes, and coercion, +combine to provide a powerful interface. -=head1 AUTHOR +If you'd like to learn more about overloading, please read the +documentation for the L pragma. -Dave Rolsky Eautarch@urth.orgE +To see all the code we created together, take a look at +F. + +=head1 NEXT STEPS + +Has this been a real project we'd probably want to: + +=over 4 + +=item Better Randomization with Crypt::Random + +=item Characteristic Base Class -=head1 COPYRIGHT AND LICENSE +=item Mutating Genes -Copyright 2006-2009 by Infinity Interactive, Inc. +=item More Characteristics -L +=item Artificial Life -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. +=back + +=head1 AUTHORS + +Aran Clary Deltac + +Dave Rolsky Eautarch@urth.orgE + +=head1 LICENSE + +This work is licensed under a Creative Commons Attribution 3.0 Unported License. + +License details are at: L =cut +