1Type::Tiny::Manual::CoeUrsceironCso(n3t)ributed Perl DocTuympeen:t:aTtiinoyn::Manual::Coercions(3)
2
3
4

NAME

6       Type::Tiny::Manual::Coercions - advanced information on coercions
7

MANUAL

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 = compile( Object, UpperCaseStr->no_coercions );
178           my ($self, $name) = $check->(@_);
179           $self->_account_name($name);
180           $self->db->update($self);
181           return $self;
182         }
183
184         # This will die instead of coercing from lowercase
185         $robert->_set_account_name('bob');
186
187   Named Coercions
188       A compromise between defining a coercion in the type library or
189       defining them in the package that uses the type library is for a type
190       library to define a named collection of coercions which can be
191       optionally added to a type constraint.
192
193         {
194           package MyApp::Types;
195           use Type::Library -base;
196           use Type::Utils qw( extends );
197
198           BEGIN { extends 'Types::Standard' };
199
200           __PACKAGE__->add_coercion(
201             name              => "FromLines",
202             type_constraint   => ArrayRef,
203             type_coercion_map => [
204               Str,     q{ [split /\n/] },
205               Undef,   q{ [] },
206             ],
207           );
208         }
209
210       This set of coercions has a name and can be imported and used:
211
212         use MyApp::Types qw( ArrayRef FromLines );
213
214         has lines => (
215           is      => 'ro',
216           isa     => ArrayRef->plus_coercions( FromLines ),
217           coerce  => 1,
218         );
219
220       Types::Standard defines a named coercion MkOpt designed to be used for
221       OptList.
222
223         use Types::Standard qw( OptList MkOpt );
224         my $OptList = OptList->plus_coercions(MkOpt);
225
226   Parameterized Coercions
227       Named coercions can also be parameterizable.
228
229         my $ArrayOfLines = ArrayRef->plus_coercions( Split[ qr{\n} ] );
230
231       Types::Standard defines Split and Join parameterizable coercions.
232
233       Viewing the source code for Types::Standard should give you hints as to
234       how they are implemented.
235
236   "Deep" Coercions
237       Certain parameterized type constraints can automatically acquire
238       coercions if their parameters have coercions. For example:
239
240          ArrayRef[ Int->plus_coercions(Num, q{int($_)}) ]
241
242       ... does what you mean!
243
244       The parameterized type constraints that do this magic include the
245       following ones from Types::Standard:
246
247       ·   ScalarRef
248
249       ·   ArrayRef
250
251       ·   HashRef
252
253       ·   Map
254
255       ·   Tuple
256
257       ·   CycleTuple
258
259       ·   Dict
260
261       ·   Optional
262
263       ·   Maybe
264
265       Imagine we're defining a type Paths in a type library:
266
267         __PACKAGE__->add_type(
268           name      => 'Paths',
269           parent    => ArrayRef[Path],
270         );
271
272       The Path type has a coercion from Str, so Paths should be able to
273       coerce from an arrayref of strings, right?
274
275       Wrong! Although ArrayRef[Path] could coerce from an arrayref of
276       strings, Paths is a separate type constraint which, although it
277       inherits from ArrayRef[Path] has its own (currently empty) set of
278       coercions.
279
280       Because that is often not what you want, Type::Tiny provides a shortcut
281       when declaring a subtype to copy the parent type constraint's
282       coercions:
283
284         __PACKAGE__->add_type(
285           name      => 'Paths',
286           parent    => ArrayRef[Path],
287           coercion  => 1,   # inherit
288         );
289
290       Now Paths can coerce from an arrayref of strings.
291
292       Deep Caveat
293
294       Currently there exists ill-defined behaviour resulting from mixing deep
295       coercions and mutable (non-frozen) coercions. Consider the following:
296
297          class_type Path, { class => "Path::Tiny" };
298          coerce Path,
299             from Str, via { "Path::Tiny"->new($_) };
300
301          declare Paths, as ArrayRef[Path], coercion => 1;
302
303          coerce Path,
304             from InstanceOf["My::File"], via { $_->get_path };
305
306       An arrayref of strings can now be coerced to an arrayref of Path::Tiny
307       objects, but is it also now possible to coerce an arrayref of My::File
308       objects to an arrayref of Path::Tiny objects?
309
310       Currently the answer is "no", but this is mostly down to implementation
311       details. It's not clear what the best way to behave in this situation
312       is, and it could start working at some point in the future.
313
314       This is why you should freeze coercions.
315
316   Chained Coercions
317       Consider the following type library:
318
319          package Types::Geometric {
320             use Type::Library -base, -declare => qw(
321                VectorArray
322                VectorArray3D
323                Point
324                Point3D
325             );
326             use Type::Utils;
327             use Types::Standard qw( Num Tuple InstanceOf );
328
329             declare VectorArray,
330                as Tuple[Num, Num];
331
332             declare VectorArray3D,
333                as Tuple[Num, Num, Num];
334
335             coerce VectorArray3D,
336                from VectorArray, via {
337                   [ @$_, 0 ];
338                };
339
340             class_type Point, { class => "Point" };
341
342             coerce Point,
343                from VectorArray, via {
344                   Point->new(x => $_->[0], y => $_->[1]);
345                };
346
347             class_type Point3D, { class => "Point3D" };
348
349             coerce Point3D,
350                from VectorArray3D, via {
351                   Point3D->new(x => $_->[0], y => $_->[1], z => $_->[2]);
352                },
353                from Point, via {
354                   Point3D->new(x => $_->x, y => $_->y, z => 0);
355                };
356          }
357
358       Given an arrayref "[1, 1]" you might reasonably expect it to be
359       coercible to a Point3D object; it matches the type constraint
360       VectorArray so can be coerced to VectorArray3D and thus to Point3D.
361
362       However, Type::Coercion does not automatically chain coercions like
363       this. Firstly, it would be incompatible with Moose's type coercion
364       system which does not chain coercions. Secondly, it's ambiguous; in our
365       example, the arrayref could be coerced along two different paths (via
366       VectorArray3D or via Point); in this case the end result would be the
367       same, but in other cases it might not. Thirdly, it runs the risk of
368       accidentally creating loops.
369
370       Doing the chaining manually though is pretty simple. Firstly, we'll
371       take note of the "coercibles" method in Type::Tiny. This method called
372       as "VectorArray3D->coercibles" returns a type constraint meaning
373       "anything that can be coerced to a VectorArray3D".
374
375       So we can define the coercions for Point3D as:
376
377          coerce Point3D,
378             from VectorArray3D->coercibles, via {
379                my $tmp = to_VectorArray3D($_);
380                Point3D->new(x => $tmp->[0], y => $tmp->[1], z => $tmp->[2]);
381             },
382             from Point, via {
383                Point3D->new(x => $_->x, y => $_->y, z => 0);
384             };
385
386       ... and now coercing from "[1, 1]" will work.
387

SEE ALSO

389       Moose::Manual::BestPractices,
390       <https://web.archive.org/web/20090624164256/http://www.catalyzed.org/2009/06/keeping-your-coercions-to-yourself.html>,
391       MooseX::Types::MoreUtils.
392

NEXT STEPS

394       After that last example, probably have a little lie down. Once you're
395       recovered, here's your next step:
396
397       ·   Type::Tiny::Manual::AllTypes
398
399           An alphabetical list of all type constraints bundled with
400           Type::Tiny.
401

AUTHOR

403       Toby Inkster <tobyink@cpan.org>.
404
406       This software is copyright (c) 2013-2014, 2017-2020 by Toby Inkster.
407
408       This is free software; you can redistribute it and/or modify it under
409       the same terms as the Perl 5 programming language system itself.
410

DISCLAIMER OF WARRANTIES

412       THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
413       WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
414       MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
415
416
417
418perl v5.32.0                      2020-09-17  Type::Tiny::Manual::Coercions(3)
Impressum