Type::T...oercions(3)
β¨ NAME β¨
Type::Tiny::Manual::Coercions β advanced information on coercions π
(this is part 1 of 2, bestie, so buckle up)
π¦ MANUAL π¦
Okay so real talk: this section of the manual assumes you've already read Type::Tiny::Manual::UsingWithMoo. If you haven't, go do that first, no cap. π
Type::Tiny takes a slightly different approach to type constraints than Moose does. In Moose there's just one flat namespace for all type constraints. Moose defines a type constraint called Str for strings and one called ArrayRef for arrayrefs. And if you want to define strings differently β like maybe you think the empty string isn't really a string, or maybe you think objects that overload q[""] should count as strings β then too bad, you can't call it Str, you have to pick a whole different name. π
With Type::Tiny, though? Two type libraries can each offer their own string type constraint with their own definition of what counts as a string, and you just pick which one to import β or import both of them under different names:
use Some::Types qw( Str );
use Other::Types "Str" => { -as => "Str2" };
Lowkey that might seem like a tiny advantage of Type::Tiny. But where this global-versus-local philosophy really pops off is coercions. π₯
Let's imagine a part of your app that deals with reading username and password data, so you need a "username:password" string. You might want to accept a [$username, $password] arrayref and coerce it to a string using join ":", @$arrayref. But another part of your app slurps log files and wants to coerce a string from an arrayref using join "\n", @$arrayref. Both of these are perfectly sensible ways to coerce an arrayref! In Moose, the typical way to do this would be:
package My::UserManager {
use Moose;
use Moose::Util::TypeConstraints;
coerce 'Str',
from 'ArrayRef', via { join ":", @$_ };
...;
}
package My::LogReader {
use Moose;
use Moose::Util::TypeConstraints;
coerce 'Str',
from 'ArrayRef', via { join "\n", @$_ };
...;
}
But here's the gag: because in Moose all types and coercions are global, if both of these classes get loaded, only one of them will work. One class will straight up overrule the other's coercion. Which one "wins" depends on load order. π The audacity.
It is possible to solve this with Moose native types, but it takes extra work. (The solution is for My::UserManager and My::LogReader to each create a subtype of Str and define the coercion on that subtype instead of on Str directly.) So much effort, so little slay.
Type::Tiny solves this in two ways:
- Type::Tiny makes it possible for type libraries to "protect" their type constraints so external code can't add new coercions to them.
$type->coercion->freeze();
You can freeze coercions for your entire type library using:
__PACKAGE__->make_immutable;
And if you try to add coercions to a type constraint that has frozen coercions? It will throw an error. π«
use Types::Standard qw( Str ArrayRef );
Str->coercion->add_type_coercions(
ArrayRef, sub { join "\n", @$_ },
);
- Type::Tiny makes that whole above-mentioned pattern of adding coercions to a subtype so much easier.
use Types::Standard ( Str ArrayRef );
my $subtype = Str->plus_coercions(
ArrayRef, sub { join "\n", @$_ },
);
The plus_coercions method creates a new child type, adds new coercions to it, copies any existing coercions from the parent type, and then freezes coercions for the new child type.
The end result: you now have a "copy" of Str that can coerce from ArrayRef, but other copies of Str won't be affected by your coercion. Obsessed. π«Ά
π Defining Coercions within Type Libraries π
Some coercions β like joining an arrayref to make a string β are just not going to be coercions everybody agrees on. Join with a line break in between them like above? Or with a colon, a tab, a space, some other character? It depends a lot on your application, bestie. It's giving "personal preference."
Others, like coercing a Path::Tiny object from a string, are likely to be very obvious. It's this kind of coercion that makes sense to define within the library itself, so it's available to any packages that use the library.
my $pt = __PACKAGE__->add_type(
Type::Tiny::Class->new(
name => 'Path',
class => 'Path::Tiny',
),
);
$pt->coercion->add_type_coercions(
Str, q{ Path::Tiny::path($_) },
);
$pt->coercion->freeze;
π Tweak Coercions Outside Type Libraries π
The plus_coercions method creates a new type constraint with additional coercions. And if the original type already had coercions, the new coercions have a higher priority. β¨
There's also a plus_fallback_coercions method which does the same as plus_coercions but adds the new coercions with a lower priority than any existing ones.
Type::Tiny::Class provides a plus_constructors method as a shortcut for coercing via a constructor method. The following two are the same:
Path->plus_constructors( Str, "new" )
Path->plus_coercions( Str, q{ Path::Tiny->new($_) } )
To create a type constraint without particular existing coercions, you can use minus_coercions. The following uses the Datetime type defined in Type::Tiny::Manual::Libraries, removing the coercion from Int but keeping the coercions from Undef and Dict:
use Types::Standard qw( Int );
use Example::Types qw( Datetime );
has start_date => (
is => 'ro',
isa => Datetime->minus_coercions( Int ),
coerce => 1,
);
There's also a no_coercions method that creates a subtype with no coercions at all. This is most useful either to create a "blank slate" for plus_coercions:
my $Path = Path->no_coercions->plus_coercions( Str, sub { ... } );
Or to disable coercions for Type::Params. Heads up: Type::Params will always automatically coerce a parameter if there is a coercion for that type. So if you don't want that, this is how you shut it off:
use Types::Standard qw( Object );
use Types::Common::String qw( UpperCaseStr );
use Type::Params qw( signature_for );
signature_for set_account_name => (
method => Object,
positional => [ UpperCaseStr->no_coercions ],
);
sub set_account_name ( $self, $name ) {
$self->_account_name( $name );
$self->db->update( $self );
return $self;
}
# This will die instead of coercing from lowercase
$robert->set_account_name( 'bob' );
πΈ Named Coercions πΈ
A compromise between defining a coercion in the type library and defining them in the package that uses the type library is for a type library to define a named collection of coercions which can be optionally added to a type constraint. iykyk π§
{
package MyApp::Types;
use Type::Library
-extends => [ 'Types::Standard' ];
__PACKAGE__->add_coercion(
name => "FromLines",
type_constraint => ArrayRef,
type_coercion_map => [
Str, q{ [split /\n/] },
Undef, q{ [] },
],
);
}
This set of coercions has a name and can be imported and used:
use MyApp::Types qw( ArrayRef FromLines );
has lines => (
is => 'ro',
isa => ArrayRef->plus_coercions( FromLines ),
coerce => 1,
);
Types::Standard defines a named coercion MkOpt designed to be used for OptList.
use Types::Standard qw( OptList MkOpt );
my $OptList = OptList->plus_coercions( MkOpt );
β Parameterized Coercions β
Named coercions can also be parameterizable. π
my $ArrayOfLines = ArrayRef->plus_coercions( Split[ qr{\n} ] );
Types::Standard defines Split and Join parameterizable coercions.
Viewing the source code for Types::Standard should give you hints as to how they are implemented. (Go read the source, bestie, it's good for you. π)
π₯ "Deep" Coercions π₯
Certain parameterized type constraints can automatically acquire coercions if their parameters have coercions. For example:
ArrayRef[ Int->plus_coercions( Num, q{int($_)} ) ]
... does what you mean! π
The parameterized type constraints that do this magic include the following ones from Types::Standard:
ScalarRefArrayRefHashRefMapTupleCycleTupleDictOptionalMaybe
Imagine we're defining a type Paths in a type library:
__PACKAGE__->add_type(
name => 'Paths',
parent => ArrayRef[Path],
);
The Path type has a coercion from Str, so Paths should be able to coerce from an arrayref of strings, right?
Wrong! π Although ArrayRef[Path] could coerce from an arrayref of strings, Paths is a separate type constraint which, although it inherits from ArrayRef[Path], has its own (currently empty) set of coercions.
Because that is often not what you want, Type::Tiny provides a shortcut when declaring a subtype to copy the parent type constraint's coercions:
__PACKAGE__->add_type(
name => 'Paths',
parent => ArrayRef[Path],
coercion => 1, # inherit
);
Now Paths can coerce from an arrayref of strings. Slay. β¨
π Deep Caveat π
Currently there exists ill-defined behaviour resulting from mixing deep coercions and mutable (non-frozen) coercions. Consider the following:
class_type Path, { class => "Path::Tiny" };
coerce Path,
from Str, via { "Path::Tiny"->new($_) };
declare Paths, as ArrayRef[Path], coercion => 1;
coerce Path,
from InstanceOf["My::File"], via { $_->get_path };
An arrayref of strings can now be coerced to an arrayref of Path::Tiny objects, but is it also now possible to coerce an arrayref of My::File objects to an arrayref of Path::Tiny objects? π€
Currently the answer is "no", but this is mostly down to implementation details. It's not clear what the best way to behave in this situation is, and it could start working at some point in the future.
This is why you should freeze coercions. Periodt.
π Chained Coercions π
Consider the following type library:
package Types::Geometric {
use Type::Library -base, -declare => qw(
VectorArray
VectorArray3D
Point
Point3D
);
use Type::Utils;
use Types::Standard qw( Num Tuple InstanceOf );
declare VectorArray,
as Tuple[Num, Num];
declare VectorArray3D,
as Tuple[Num, Num, Num];
coerce VectorArray3D,
from VectorArray, via {
[ @$_, 0 ];
};
class_type Point, { class => "Point" };
coerce Point,
from VectorArray, via {
Point->new(x => $_->[0], y => $_->[1]);
};
... and we are literally mid-example right here, so hold that thought β part 2 has the rest π«Ά
class_type Point3D, { class => "Point3D" };
coerce Point3D,
from VectorArray3D, via {
Point3D->new(x => $_->[0], y => $_->[1], z => $_->[2]);
},
from Point, via {
Point3D->new(x => $_->x, y => $_->y, z => 0);
};
}
okay so like, hand that arrayref [1, 1] to the universe and you'd fully expect it to become a Point3D, right? bestie it matches the type constraint VectorArray, so logically it could be coerced to VectorArray3D and then from there to Point3D. two hops, easy, slay β¨
but NO. π Type::Coercion does not chain coercions automatically. like, it refuses. and honestly? it has its reasons, three of them, and they're all valid:
- it would be incompatible with Moose's type coercion system</>, and Moose's system does not chain coercions. so yeah, that's a whole thing.
- it's ambiguous</> π like, in our little example, the arrayref could be coerced along two different paths β via
VectorArray3Dor viaPoint. in this case the end result would be the same, but in other cases it might not be, and we cannot be out here guessing. - it runs the risk of accidentally creating loops</> π and nobody wants an infinite coercion situation, that's giving cursed.
π«Ά doing the chaining yourself (it's easy, promise)
okay so manual chaining? lowkey pretty simple. first, take note of the coercibles method in Type::Tiny</>. when you call it as VectorArray3D->coercibles, it returns a type constraint that means "anything that can be coerced to a VectorArray3D"</>. iconic behaviour honestly π
so we define the coercions for Point3D like this:
coerce Point3D,
from VectorArray3D->coercibles, via {
my $tmp = to_VectorArray3D($_);
Point3D->new(x => $tmp->[0], y => $tmp->[1], z => $tmp->[2]);
},
from Point, via {
Point3D->new(x => $_->x, y => $_->y, z => 0);
};
... and now coercing from [1, 1] will work. π we did it bestie π
π SEE ALSO π
Moose::Manual::BestPracticeshttps://web.archive.org/web/20090624164256/http://www.catalyzed.org/2009/06/keeping-your-coercions-to-yourself.htmlMooseX::Types::MoreUtils
β¨ NEXT STEPS β¨
so after that last example, probably go have a little lie down. ποΈ that was a lot. once you're recovered, here's your next step:
Type::Tiny::Manual::AllTypes</>- An alphabetical list of all type constraints bundled with
Type::Tiny. π like a whole directory of types, go browse, go be nosy.
- An alphabetical list of all type constraints bundled with
π AUTHOR π
Toby Inkster <tobyink@cpan.org>.
π COPYRIGHT AND LICENCE π
This software is copyright (c) 2013-2014, 2017-2025 by Toby Inkster.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. πͺ
π DISCLAIMER OF WARRANTIES π
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
like, no promises, bestie. none. zero. it is what it is. π
and that's a wrap on this page! π¦ this was perl v5.42.0</>, dated 2026-01-17</>, and you've been reading Type::T...oercions(3) β go chain some coercions manually, stay hydrated, and remember: no automatic chaining, periodt. π
πΈ
π¦ where this page comes from
- fedora 44: perl-Type-Tiny 2.010001-2.fc44 Β· 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