From: Stevan Little Date: Fri, 21 Jul 2006 15:03:45 +0000 (+0000) Subject: recipe 5 X-Git-Tag: 0_12~15 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=50ec505572d266fe4e5795bfa37f68b21f087ae2;p=gitmo%2FMoose.git recipe 5 --- diff --git a/lib/Moose/Cookbook/Recipe5.pod b/lib/Moose/Cookbook/Recipe5.pod index c3b0368..e352246 100644 --- a/lib/Moose/Cookbook/Recipe5.pod +++ b/lib/Moose/Cookbook/Recipe5.pod @@ -17,32 +17,34 @@ Moose::Cookbook::Recipe5 - More subtypes, coercion in a B class use Params::Coerce (); use URI (); - subtype Header - => as Object + subtype 'Header' + => as 'Object' => where { $_->isa('HTTP::Headers') }; - coerce Header - => from ArrayRef + coerce 'Header' + => from 'ArrayRef' => via { HTTP::Headers->new( @{ $_ } ) } - => from HashRef + => from 'HashRef' => via { HTTP::Headers->new( %{ $_ } ) }; - subtype Uri - => as Object + subtype 'Uri' + => as 'Object' => where { $_->isa('URI') }; - coerce Uri - => from Object - => via { $_->isa('URI') ? $_ : Params::Coerce::coerce( 'URI', $_ ) } - => from Str + coerce 'Uri' + => from 'Object' + => via { $_->isa('URI') + ? $_ + : Params::Coerce::coerce( 'URI', $_ ) } + => from 'Str' => via { URI->new( $_, 'http' ) }; - subtype Protocol + subtype 'Protocol' => as Str => where { /^HTTP\/[0-9]\.[0-9]$/ }; has 'base' => (is => 'rw', isa => 'Uri', coerce => 1); - has 'url' => (is => 'rw', isa => 'Uri', coerce => 1); + has 'uri' => (is => 'rw', isa => 'Uri', coerce => 1); has 'method' => (is => 'rw', isa => 'Str'); has 'protocol' => (is => 'rw', isa => 'Protocol'); has 'headers' => ( @@ -54,9 +56,134 @@ Moose::Cookbook::Recipe5 - More subtypes, coercion in a B class =head1 DESCRIPTION -Coming Soon. +This recipe introduces the idea of type coercions, and the C +keyword. Coercions can be attached to pre-existing type constraints, +and can be used to transform input of one type, into input of another +type. This can be an extremely powerful tool if used correctly, which +is why, by default, it is off. If you want your accessor to attempt +a coercion, you must specifically ask for it with the B option. -(the other 4 recipes kinda burned me out a bit) +Now, onto the coercions. + +First we need to create a subtype to attach our coercion too. Here we +create a basic I
subtype, which matches any instance of the +class B. + + subtype 'Header' + => as 'Object' + => where { $_->isa('HTTP::Headers') }; + +The simplest thing from here would be create an accessor declaration +like so: + + has 'headers' => ( + is => 'rw', + isa => 'Header', + default => sub { HTTP::Headers->new } + ); + +We would then have a self-validating accessor whose default value is +an empty instance of B. This is nice, but it is not +ideal. + +The constructor for B accepts a list of key-value pairs +representing the fields in an HTTP header. With Perl such a list could +easily be stored into an ARRAY or HASH reference. We would like our +class's interface to be able to accept this list of key-value pairs +in place of the B instance, and just DWIM. This is where +coercion can help. First, lets declare our coercion: + + coerce 'Header' + => from 'ArrayRef' + => via { HTTP::Headers->new( @{ $_ } ) } + => from 'HashRef' + => via { HTTP::Headers->new( %{ $_ } ) }; + +We first tell it that we are attaching the coercion to the 'Header' +subtype. We then give is a set of C clauses which map other +subtypes to coercion routines (through the C keyword). Fairly +simple really, however, this alone does nothing. We have to tell +our attribute declaration to actually use the coercion, like so: + + has 'headers' => ( + is => 'rw', + isa => 'Header', + coerce => 1, + default => sub { HTTP::Headers->new } + ); + +This will coerce any B or B which is passed into +the C accessor into an instance of B. So that +the following lines of code are all equivalent: + + $foo->headers(HTTP::Headers->new(bar => 1, baz => 2)); + $foo->headers([ 'bar', 1, 'baz', 2 ]); + $foo->headers({ bar => 1, baz => 2 }); + +As you can see, careful use of coercions can produce an very open +interface for your class, while still retaining the "safety" of +your type constraint checks. + +Our next coercion takes advantage of the power of CPAN to handle +the details of our coercion. In this particular case it uses the +L module, which fits in rather nicely with L. + +Again, we create a simple subtype to represent instance of the +B class. + + subtype 'Uri' + => as 'Object' + => where { $_->isa('URI') }; + +Then we add the coercion: + + coerce 'Uri' + => from 'Object' + => via { $_->isa('URI') + ? $_ + : Params::Coerce::coerce( 'URI', $_ ) } + => from 'Str' + => via { URI->new( $_, 'http' ) }; + +The first C clause we introduce is for the 'Object' subtype, +an 'Object' is simply, anything which is Ced. This means +that if the coercion encounters another object, it should use this +clause. Now we look at the C block so what it does. First +it checks to see if its a B instance. Since the coercion +process happens prior to any type constraint checking, it is entirely +possible for this to happen. And if it does happen, we simple want +to pass the instance on through. However, if it is not an instance +of B, then we need to coerce it. This is where L +can do it's magic, and we can just use it's return value. Simple +really, and much less work since we use a module from CPAN :) + +The second C clause is attached to the 'Str' subtype, and +illustrates how coercions can also be used to handle certain +'default' behaviors. In this coercion, we simple take any string +and pass it into the B constructor along with the default +'http' scheme type. + +And of course, our coercions do nothing unless they are told to, +like so: + + has 'base' => (is => 'rw', isa => 'Uri', coerce => 1); + has 'uri' => (is => 'rw', isa => 'Uri', coerce => 1); + +As you can see, re-using the coercion allows us to enforce a +consistent and very flexible API across multiple accessors. + +=head1 CONCLUSION + +This recipe illustrated the power of coercions to build a more +flexible and open API for your accessors, while still retaining +all the safety that comes from using Moose's type constraints. +Using coercions it becomes simple to manage (from a single +location) a consisten API not only across multiple accessors, +but across multiple classes as well. + +In the next recipe, we will introduce roles, a concept originally +borrowed from Smalltalk, which made it's way into Perl 6, and +now into Moose. =head1 AUTHOR