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 = 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
248ScalarRef
249
250ArrayRef
251
252HashRef
253
254Map
255
256Tuple
257
258CycleTuple
259
260Dict
261
262Optional
263
264Maybe
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

SEE ALSO

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

NEXT STEPS

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

AUTHOR

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

DISCLAIMER OF WARRANTIES

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-01-04  Type::Tiny::Manual::Coercions(3)
Impressum