Moose::...Coercion(3)
π¦ Moose::Cookbook::Basics::HTTP_SubtypesAndCoercion π¦
okay bestie, so this one lives in the User Contributed Perl Documentation, and it's a cookbook recipe β a little tutorial situation β all about making your HTTP-ish classes (Request, Protocol, etc.) accept whatever you throw at them. it's giving β¨flexible APIβ¨ and honestly? iconic.
πΈ NAME πΈ
Moose::Cookbook::Basics::HTTP_SubtypesAndCoercion β it demonstrates subtypes and coercion, using HTTP-related classes (Request, Protocol, etc.) as the vibe. π
π VERSION π
version 2.4000 β yes queen, that's the version, no cap.
β¨ SYNOPSIS β¨
okay so here's the whole thing laid out, and you're gonna want to actually read it because it slays:
package Request;
use Moose;
use Moose::Util::TypeConstraints;
use HTTP::Headers ();
use Params::Coerce ();
use URI ();
subtype 'My::Types::HTTP::Headers' => as class_type('HTTP::Headers');
coerce 'My::Types::HTTP::Headers'
=> from 'ArrayRef'
=> via { HTTP::Headers->new( @{$_} ) }
=> from 'HashRef'
=> via { HTTP::Headers->new( %{$_} ) };
subtype 'My::Types::URI' => as class_type('URI');
coerce 'My::Types::URI'
=> from 'Object'
=> via { $_->isa('URI')
? $_
: Params::Coerce::coerce( 'URI', $_ ); }
=> from 'Str'
=> via { URI->new( $_, 'http' ) };
subtype 'Protocol'
=> as 'Str'
=> where { /^HTTP\/[0-9]\.[0-9]$/ };
has 'base' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
has 'uri' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
has 'method' => ( is => 'rw', isa => 'Str' );
has 'protocol' => ( is => 'rw', isa => 'Protocol' );
has 'headers' => (
is => 'rw',
isa => 'My::Types::HTTP::Headers',
coerce => 1,
default => sub { HTTP::Headers->new }
);
π DESCRIPTION π
so this recipe is all about type coercions β the way that's defined is with the coerce sugar function. basically, a coercion gets attached to a type constraint you already have, and it defines a transformation (one-way only, bestie, it does not go both ways) from one type into another. π¦
it's very powerful, but lowkey it can also have consequences you were NOT expecting, so you have to explicitly ask for an attribute to get coerced. how? you set the coerce attribute option to a true value. that's the whole ask. π
first up, we create the subtype we're gonna coerce the other types into:
subtype 'My::Types::HTTP::Headers' => as class_type('HTTP::Headers');
we're making a subtype here instead of just using HTTP::Headers as a type directly. why?? because coercions are global, babes. if we defined a coercion for HTTP::Headers in our Request class, it would then be defined for every single Moose-using class in the current Perl interpreter. that's a whole lot of chaos for no reason. avoiding that kind of namespace pollution is a best practice, periodt. π«Ά
the class_type sugar function is literally just a shortcut for this:
subtype 'HTTP::Headers'
=> as 'Object'
=> where { $_->isa('HTTP::Headers') };
so internally, Moose creates a type constraint for every class that uses Moose β but for non-Moose classes, you gotta declare the type yourself. no shortcuts there, sorry. π
we could just use this new type directly:
has 'headers' => (
is => 'rw',
isa => 'My::Types::HTTP::Headers',
default => sub { HTTP::Headers->new }
);
that creates a simple attribute which defaults to an empty instance of HTTP::Headers. cute, but basic. π
now β the constructor for HTTP::Headers accepts a list of key-value pairs representing the HTTP header fields. and in Perl, a list like that could be stored in an ARRAY or a HASH reference. so we want our headers attribute to accept those data structures instead of an HTTP::Headers instance, and just do the right thing automatically. that is exactly what coercion is for:
coerce 'My::Types::HTTP::Headers'
=> from 'ArrayRef'
=> via { HTTP::Headers->new( @{$_} ) }
=> from 'HashRef'
=> via { HTTP::Headers->new( %{$_} ) };
the first argument to coerce is the type we're coercing to. then we hand it a set of from/via clauses. the from function takes some other type name, and via takes a subroutine reference that actually does the coercion. simple as that. β¨
but here's the thing β defining the coercion does absolutely nothing until we tell Moose we want a particular attribute to be coerced:
has 'headers' => (
is => 'rw',
isa => 'My::Types::HTTP::Headers',
coerce => 1,
default => sub { HTTP::Headers->new }
);
now, if we use an ArrayRef or a HashRef to populate headers, it gets coerced into a brand new HTTP::Headers instance. with the coercion in place, all of these lines are equivalent:
$foo->headers( HTTP::Headers->new( bar => 1, baz => 2 ) );
$foo->headers( [ 'bar', 1, 'baz', 2 ] );
$foo->headers( { bar => 1, baz => 2 } );
see?? careful use of coercions can produce a very open interface for your class, while still keeping all the "safety" of your type constraint checks. (1) π
our next coercion shows how we can leverage existing CPAN modules to help implement coercions. in this case, we're using Params::Coerce. π§
once again, we need to declare a class type for our non-Moose URI class:
subtype 'My::Types::URI' => as class_type('URI');
then we define the coercion:
coerce 'My::Types::URI'
=> from 'Object'
=> via { $_->isa('URI')
? $_
: Params::Coerce::coerce( 'URI', $_ ); }
=> from 'Str'
=> via { URI->new( $_, 'http' ) };
the first coercion takes any object and makes it a URI object. here's the tea though β the coercion system isn't that smart, and it does not check whether the object is already a URI, so we have to check for that ourselves. if it's not a URI already, we let Params::Coerce do its magic and we just use its return value. πͺ
and if Params::Coerce didn't return a URI object (for whatever reason), Moose would throw a type constraint error. so, y'know. heads up. β
the other coercion takes a string and converts it to a URI. in this case we're using the coercion to apply a default behavior, where a string is assumed to be an "http" URI. very demure, very mindful. π
finally, we need to make sure our attributes actually enable coercion:
has 'base' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
has 'uri' => ( is => 'rw', isa => 'My::Types::URI', coerce => 1 );
re-using the coercion lets us enforce a consistent API across multiple attributes. efficiency, bestie. π
π₯ CONCLUSION π₯
this recipe showed the use of coercions to create a more flexible and DWIM-y API. and like any powerful feature β we recommend some caution. sometimes it's genuinely better to reject a value than to just guess at how to DWIM it. no cap.
we also showed the use of the class_type sugar function as a shortcut for defining a new subtype of Object. slay. π
π FOOTNOTES π
- (1) this particular example could be safer. really, we only want to coerce an array with an even number of elements. we could create a new
EvenElementArrayReftype, and then coerce from that type, as opposed to a plainArrayRef. β¨
π AUTHORS π
look at this lineup, absolute legends the whole way down:
- Stevan Little
<stevan@cpan.org> - Dave Rolsky
<autarch@urth.org> - Jesse Luehrs
<doy@cpan.org> - Shawn M Moore
<sartak@cpan.org> - ΧΧΧΧ Χ§ΧΧ'ΧΧ (Yuval Kogman)
<nothingmuch@woobling.org> - Karen Etheridge
<ether@cpan.org> - Florian Ragwitz
<rafl@debian.org> - Hans Dieter Pearcey
<hdp@cpan.org> - Chris Prather
<chris@prather.org> - Matt S Trout
<mstrout@cpan.org>
π COPYRIGHT AND LICENSE π
this software is copyright (c) 2006 by Infinity Interactive, Inc.
this is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. π«Ά
and that's the whole page, bestie!! π this doc shipped as part of perl v5.42.0, dated 2025-07-25 β and it's filed under Moose::...Coercion(3). go forth and coerce responsibly. β¨π¦π
π¦ where this page comes from
- fedora 43: perl-Moose 2.4000-4.fc43 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms