1Type::Tiny::Manual::CoeUrsceironCso(n3t)ributed Perl DocTuympeen:t:aTtiinoyn::Manual::Coercions(3)
2
3
4
6 Type::Tiny::Manual::Coercions - advanced information on coercions
7
9 This section of the manual assumes you've already read
10 Type::Tiny::Manual::UsingWithMoo.
11
12 Type::Tiny takes a slightly different approach to type constraints from
13 Moose. In Moose, there is a single flat namespace for type
14 constraints. Moose defines a type constraint called Str for strings and
15 a type constraint called ArrayRef for arrayrefs. If you want to define
16 strings differently (maybe you think that the empty string doesn't
17 really count as a string, or maybe you think objects overloading
18 "q[""]" should count as strings) then you can't call it Str; you need
19 to choose a different name.
20
21 With Type::Tiny, two type libraries can each offer a string type
22 constraint with their own definitions for what counts as a string, and
23 you can choose which one to import, or import them both with different
24 names:
25
26 use Some::Types qw( Str );
27 use Other::Types "Str" => { -as => "Str2" };
28
29 This might seem to be a small advantage of Type::Tiny, but where this
30 global-versus-local philosophy really makes a difference is coercions.
31
32 Let's imagine for a part of your application that deals with reading
33 username and password data you need to have a "username:password"
34 string. You may wish to accept a "[$username, $password]" arrayref and
35 coerce it to a string using "join ":", @$arrayref". But another part of
36 your application deals with slurping log files, and wants to coerce a
37 string from an arrayref using "join "\n", @$arrayref". These are both
38 perfectly sensible ways to coerce an arrayref. In Moose, a typical way
39 to do this would be:
40
41 package My::UserManager {
42 use Moose;
43 use Moose::Util::TypeConstraints;
44
45 coerce 'Str',
46 from 'ArrayRef', via { join ":", @$_ };
47
48 ...;
49 }
50
51 package My::LogReader {
52 use Moose;
53 use Moose::Util::TypeConstraints;
54
55 coerce 'Str',
56 from 'ArrayRef', via { join "\n", @$_ };
57
58 ...;
59 }
60
61 However, because in Moose all types and coercions are global, if both
62 these classes are loaded, only one of them will work. One class will
63 overrule the other's coercion. Which one "wins" will depend on load
64 order.
65
66 It is possible to solve this with Moose native types, but it requires
67 extra work. (The solution is for My::UserManager and My::LogReader to
68 each create a subtype of Str and define the coercion on that subtype
69 instead of on Str directly.)
70
71 Type::Tiny solves this in two ways:
72
73 1. Type::Tiny makes it possible for type libraries to "protect" their
74 type constraints to prevent external code from adding new coercions
75 to them.
76
77 $type->coercion->freeze();
78
79 You can freeze coercions for your entire type library using:
80
81 __PACKAGE__->make_immutable;
82
83 If you try to add coercions to a type constraint that has frozen
84 coercions, it will throw an error.
85
86 use Types::Standard qw( Str ArrayRef );
87
88 Str->coercion->add_type_coercions(
89 ArrayRef, sub { join "\n", @$_ },
90 );
91
92 2. Type::Tiny makes the above-mentioned pattern of adding coercions to
93 a subtype much easier.
94
95 use Types::Standard ( Str ArrayRef );
96
97 my $subtype = Str->plus_coercions(
98 ArrayRef, sub { join "\n", @$_ },
99 );
100
101 The "plus_coercions" method creates a new child type, adds new
102 coercions to it, copies any existing coercions from the parent
103 type, and then freezes coercions for the new child type.
104
105 The end result is you now have a "copy" of Str that can coerce from
106 ArrayRef but other copies of Str won't be affected by your
107 coercion.
108
109 Defining Coercions within Type Libraries
110 Some coercions like joining an arrayref to make a string are not going
111 to be coercions that everybody will agree on. Join with a line break in
112 between them as above? Or with a colon, a tab, a space, some other
113 chanaracter? It depends a lot on your application.
114
115 Others, like coercing a Path::Tiny object from a string, are likely to
116 be very obvious. It is this kind of coercion that it makes sense to
117 define within the library itself so it's available to any packages that
118 use the library.
119
120 my $pt = __PACKAGE__->add_type(
121 Type::Tiny::Class->new(
122 name => 'Path',
123 class => 'Path::Tiny',
124 ),
125 );
126
127 $pt->coercion->add_type_coercions(
128 Str, q{ Path::Tiny::path($_) },
129 );
130
131 $pt->coercion->freeze;
132
133 Tweak Coercions Outside Type Libraries
134 The "plus_coercions" method creates a new type constraint with
135 additional coercions. If the original type already had coercions, the
136 new coercions have a higher priority.
137
138 There's also a "plus_fallback_coercions" method which does the same as
139 "plus_coercions" but adds the new coercions with a lower priority than
140 any existing ones.
141
142 Type::Tiny::Class provides a "plus_constructors" method as a shortcut
143 for coercing via a constructor method. The following two are the same:
144
145 Path->plus_constructors( Str, "new" )
146
147 Path->plus_coercions( Str, q{ Path::Tiny->new($_) } )
148
149 To create a type constraint without particular existing coercions, you
150 can use "minus_coercions". The following uses the Datetime type defined
151 in Type::Tiny::Manual::Libraries, removing the coercion from Int but
152 keeping the coercions from Undef and Dict.
153
154 use Types::Standard qw( Int );
155 use Example::Types qw( Datetime );
156
157 has start_date => (
158 is => 'ro',
159 isa => Datetime->minus_coercions( Int ),
160 coerce => 1,
161 );
162
163 There's also a "no_coercions" method that creates a subtype with no
164 coercions at all. This is most useful either to create a "blank slate"
165 for "plus_coercions":
166
167 my $Path = Path->no_coercions->plus_coercions( Str, sub { ... } );
168
169 Or to disable coercions for Type::Params. Type::Params will always
170 automatically coerce a parameter if there is a coercion for that type.
171
172 use Types::Standard qw( Object );
173 use Types::Common::String qw( UpperCaseStr );
174 use Type::Params;
175
176 sub set_account_name {
177 state $check = signature(
178 method => Object,
179 positional => [ UpperCaseStr->no_coercions ],
180 );
181 my ( $self, $name ) = $check->( @_ );
182 $self->_account_name( $name );
183 $self->db->update( $self );
184 return $self;
185 }
186
187 # This will die instead of coercing from lowercase
188 $robert->set_account_name( 'bob' );
189
190 Named Coercions
191 A compromise between defining a coercion in the type library or
192 defining them in the package that uses the type library is for a type
193 library to define a named collection of coercions which can be
194 optionally added to a type constraint.
195
196 {
197 package MyApp::Types;
198 use Type::Library
199 -extends => [ 'Types::Standard' ];
200
201 __PACKAGE__->add_coercion(
202 name => "FromLines",
203 type_constraint => ArrayRef,
204 type_coercion_map => [
205 Str, q{ [split /\n/] },
206 Undef, q{ [] },
207 ],
208 );
209 }
210
211 This set of coercions has a name and can be imported and used:
212
213 use MyApp::Types qw( ArrayRef FromLines );
214
215 has lines => (
216 is => 'ro',
217 isa => ArrayRef->plus_coercions( FromLines ),
218 coerce => 1,
219 );
220
221 Types::Standard defines a named coercion MkOpt designed to be used for
222 OptList.
223
224 use Types::Standard qw( OptList MkOpt );
225 my $OptList = OptList->plus_coercions( MkOpt );
226
227 Parameterized Coercions
228 Named coercions can also be parameterizable.
229
230 my $ArrayOfLines = ArrayRef->plus_coercions( Split[ qr{\n} ] );
231
232 Types::Standard defines Split and Join parameterizable coercions.
233
234 Viewing the source code for Types::Standard should give you hints as to
235 how they are implemented.
236
237 "Deep" Coercions
238 Certain parameterized type constraints can automatically acquire
239 coercions if their parameters have coercions. For example:
240
241 ArrayRef[ Int->plus_coercions( Num, q{int($_)} ) ]
242
243 ... does what you mean!
244
245 The parameterized type constraints that do this magic include the
246 following ones from Types::Standard:
247
248 • ScalarRef
249
250 • ArrayRef
251
252 • HashRef
253
254 • Map
255
256 • Tuple
257
258 • CycleTuple
259
260 • Dict
261
262 • Optional
263
264 • Maybe
265
266 Imagine we're defining a type Paths in a type library:
267
268 __PACKAGE__->add_type(
269 name => 'Paths',
270 parent => ArrayRef[Path],
271 );
272
273 The Path type has a coercion from Str, so Paths should be able to
274 coerce from an arrayref of strings, right?
275
276 Wrong! Although ArrayRef[Path] could coerce from an arrayref of
277 strings, Paths is a separate type constraint which, although it
278 inherits from ArrayRef[Path] has its own (currently empty) set of
279 coercions.
280
281 Because that is often not what you want, Type::Tiny provides a shortcut
282 when declaring a subtype to copy the parent type constraint's
283 coercions:
284
285 __PACKAGE__->add_type(
286 name => 'Paths',
287 parent => ArrayRef[Path],
288 coercion => 1, # inherit
289 );
290
291 Now Paths can coerce from an arrayref of strings.
292
293 Deep Caveat
294
295 Currently there exists ill-defined behaviour resulting from mixing deep
296 coercions and mutable (non-frozen) coercions. Consider the following:
297
298 class_type Path, { class => "Path::Tiny" };
299 coerce Path,
300 from Str, via { "Path::Tiny"->new($_) };
301
302 declare Paths, as ArrayRef[Path], coercion => 1;
303
304 coerce Path,
305 from InstanceOf["My::File"], via { $_->get_path };
306
307 An arrayref of strings can now be coerced to an arrayref of Path::Tiny
308 objects, but is it also now possible to coerce an arrayref of My::File
309 objects to an arrayref of Path::Tiny objects?
310
311 Currently the answer is "no", but this is mostly down to implementation
312 details. It's not clear what the best way to behave in this situation
313 is, and it could start working at some point in the future.
314
315 This is why you should freeze coercions.
316
317 Chained Coercions
318 Consider the following type library:
319
320 package Types::Geometric {
321 use Type::Library -base, -declare => qw(
322 VectorArray
323 VectorArray3D
324 Point
325 Point3D
326 );
327 use Type::Utils;
328 use Types::Standard qw( Num Tuple InstanceOf );
329
330 declare VectorArray,
331 as Tuple[Num, Num];
332
333 declare VectorArray3D,
334 as Tuple[Num, Num, Num];
335
336 coerce VectorArray3D,
337 from VectorArray, via {
338 [ @$_, 0 ];
339 };
340
341 class_type Point, { class => "Point" };
342
343 coerce Point,
344 from VectorArray, via {
345 Point->new(x => $_->[0], y => $_->[1]);
346 };
347
348 class_type Point3D, { class => "Point3D" };
349
350 coerce Point3D,
351 from VectorArray3D, via {
352 Point3D->new(x => $_->[0], y => $_->[1], z => $_->[2]);
353 },
354 from Point, via {
355 Point3D->new(x => $_->x, y => $_->y, z => 0);
356 };
357 }
358
359 Given an arrayref "[1, 1]" you might reasonably expect it to be
360 coercible to a Point3D object; it matches the type constraint
361 VectorArray so can be coerced to VectorArray3D and thus to Point3D.
362
363 However, Type::Coercion does not automatically chain coercions like
364 this. Firstly, it would be incompatible with Moose's type coercion
365 system which does not chain coercions. Secondly, it's ambiguous; in our
366 example, the arrayref could be coerced along two different paths (via
367 VectorArray3D or via Point); in this case the end result would be the
368 same, but in other cases it might not. Thirdly, it runs the risk of
369 accidentally creating loops.
370
371 Doing the chaining manually though is pretty simple. Firstly, we'll
372 take note of the "coercibles" method in Type::Tiny. This method called
373 as "VectorArray3D->coercibles" returns a type constraint meaning
374 "anything that can be coerced to a VectorArray3D".
375
376 So we can define the coercions for Point3D as:
377
378 coerce Point3D,
379 from VectorArray3D->coercibles, via {
380 my $tmp = to_VectorArray3D($_);
381 Point3D->new(x => $tmp->[0], y => $tmp->[1], z => $tmp->[2]);
382 },
383 from Point, via {
384 Point3D->new(x => $_->x, y => $_->y, z => 0);
385 };
386
387 ... and now coercing from "[1, 1]" will work.
388
390 Moose::Manual::BestPractices,
391 <https://web.archive.org/web/20090624164256/http://www.catalyzed.org/2009/06/keeping-your-coercions-to-yourself.html>,
392 MooseX::Types::MoreUtils.
393
395 After that last example, probably have a little lie down. Once you're
396 recovered, here's your next step:
397
398 • Type::Tiny::Manual::AllTypes
399
400 An alphabetical list of all type constraints bundled with
401 Type::Tiny.
402
404 Toby Inkster <tobyink@cpan.org>.
405
407 This software is copyright (c) 2013-2014, 2017-2023 by Toby Inkster.
408
409 This is free software; you can redistribute it and/or modify it under
410 the same terms as the Perl 5 programming language system itself.
411
413 THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
414 WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
415 MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
416
417
418
419perl v5.36.0 2023-04-24 Type::Tiny::Manual::Coercions(3)