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

NAME

6       Type::Tiny::Manual::Coercions - adding coercions to type constraints
7

DESCRIPTION

9       Stop! Don't do it!
10
11       OK, it's fairly common practice in Moose/Mouse code to define coercions
12       for type constraints. For example, suppose we define a type constraint
13       in a type library:
14
15          class_type PathTiny, { class => "Path::Tiny" };
16
17       We may wish to define a coercion (i.e. a conversion routine) to handle
18       strings, and convert them into Path::Tiny objects:
19
20          coerce PathTiny,
21             from Str, via { "Path::Tiny"->new($_) };
22
23       However, there are good reasons to avoid this practice. It ties the
24       coercion routine to the type constraint. Any people wishing to use your
25       "PathTiny" type constraint need to buy in to your idea of how they
26       should be coerced from "Str". With Path::Tiny this is unlikely to be
27       controversial, however consider:
28
29          coerce ArrayRef,
30             from Str, via { [split /\n/] };
31
32       In one part of the application (dealing with parsing log files for
33       instance), this could be legitimate. But another part (dealing with
34       logins perhaps) might prefer to split on colons. Another (dealing with
35       web services) might attempt to parse the string as a JSON array.
36
37       If all these coercions have attached themselves to the "ArrayRef" type
38       constraint, coercing a string becomes a complicated proposition!  In a
39       large application where coercions are defined across many different
40       files, the application can start to suffer from "spooky action at a
41       distance".
42
43       In the interests of Moose-compatibility, Type::Tiny and Type::Coercion
44       do allow you to define coercions this way, but they also provide an
45       alternative that you should consider: "plus_coercions".
46
47   plus_coercions
48       Type::Tiny offers a method "plus_coercions" which constructs a new
49       anonymous type constraint, but with additional coercions.
50
51       In our earlier example, we'd define the "PathTiny" type constraint in
52       our type library as before:
53
54          class_type PathTiny, { class => "Path::Tiny" };
55
56       But then not define any coercions for it. Later, when using the type
57       constraint in a class, we can add coercions:
58
59          my $ConfigFileType = PathTiny->plus_coercions(
60             Str,   sub { "Path::Tiny"->new($_) },
61             Undef, sub { "Path::Tiny"->new("/etc/myapp/default.conf") },
62          );
63
64          has config_file => (
65             is     => "ro",
66             isa    => $ConfigFileType,
67             coerce => 1,
68          );
69
70       Where the "PathTiny" constraint is used in another part of the code, it
71       will not see these coercions, because they were added to the new
72       anonymous type constraint, not to the "PathTiny" constraint itself!
73
74   Named Coercions
75       A type library may define a named set of coercions to a particular
76       type. For example, let's define that coercion from "Str" to "ArrayRef":
77
78          declare_coercion "LinesFromStr",
79             to_type ArrayRef,
80             from Str, q{ [split /\n/] };
81
82       Now we can import that coercion using a name, and it makes our code
83       look a little cleaner:
84
85          use Types::Standard qw(ArrayRef);
86          use MyApp::Types qw(LinesFromStr);
87
88          has lines => (
89             is     => "ro",
90             isa    => ArrayRef->plus_coercions(LinesFromStr),
91             coerce => 1,
92          );
93
94   Parameterized Coercions
95       Parameterized type constraints are familiar from Moose. For example, an
96       arrayref of integers:
97
98          ArrayRef[Int]
99
100       Type::Coercion supports parameterized named coercions too. For example,
101       the following type constraint has a coercion from strings that splits
102       them into lines:
103
104          use Types::Standard qw( ArrayRef Split );
105
106          my $ArrayOfLines = ArrayRef->plus_coercions( Split[ qr{\n} ] );
107
108       Viewing the source code for Types::Standard should give you hints as to
109       how they are implemented.
110
111   plus_fallback_coercions, minus_coercions and no_coercions
112       Getting back to the "plus_coercions" method, there are some other
113       methods that perform coercion maths.
114
115       "plus_fallback_coercions" is the same as "plus_coercions" but the added
116       coercions have a lower priority than any existing coercions.
117
118       "minus_coercions" can be given a list of type constraints that we wish
119       to ignore coercions for. Imagine our "PathTiny" constraint already has
120       a coercion from "Str", then the following creates a new anonymous type
121       constraint without that coercion:
122
123          PathTiny->minus_coercions(Str)
124
125       "no_coercions" gives us a new type anonymous constraint without any of
126       its parents coercions. This is useful as a way to create a blank slate
127       for a subsequent "plus_coercions":
128
129          PathTiny->no_coercions->plus_coercions(...)
130
131   plus_constructors
132       The "plus_constructors" method defined in Type::Tiny::Class is sugar
133       for "plus_coercions". The following two are the same:
134
135          PathTiny->plus_coercions(Str, q{ Path::Tiny->new($_) })
136
137          PathTiny->plus_constructors(Str, "new");
138
139   "Deep" Coercions
140       Certain parameterized type constraints can automatically acquire
141       coercions if their parameters have coercions. For example:
142
143          ArrayRef[ Int->plus_coercions(Num, q{int($_)}) ]
144
145       ... does what you mean!
146
147       The parameterized type constraints that do this magic include the
148       following ones from Types::Standard:
149
150       ·   "ScalarRef"
151
152       ·   "ArrayRef"
153
154       ·   "HashRef"
155
156       ·   "Map"
157
158       ·   "Tuple"
159
160       ·   "CycleTuple"
161
162       ·   "Dict"
163
164       ·   "Optional"
165
166       ·   "Maybe"
167
168       Imagine we're declaring a type library:
169
170          declare Paths, as ArrayRef[PathTiny];
171
172       The "PathTiny" type (declared earlier in the tutorial) has a coercion
173       from "Str", so "Paths" should be able to coerce from an arrayref of
174       strings, right?
175
176       Wrong! "ArrayRef[PathTiny]" can coerce from an arrayref of strings, but
177       "Paths" is a separate type constraint which, although it inherits from
178       "ArrayRef[PathTiny]" has its own (currently empty) set of coercions.
179
180       Because that is often not what you want, Type::Tiny provides a shortcut
181       when declaring a subtype to copy the parent type constraint's
182       coercions:
183
184          declare Paths, as ArrayRef[PathTiny], coercion => 1;
185
186       Now "Paths" can coerce from an arrayref of strings.
187
188       Deep Caveat
189
190       Currently there exists ill-defined behaviour resulting from mixing deep
191       coercions and mutable (non-frozen) coercions. Consider the following:
192
193          class_type PathTiny, { class => "Path::Tiny" };
194          coerce PathTiny,
195             from Str, via { "Path::Tiny"->new($_) };
196
197          declare Paths, as ArrayRef[PathTiny], coercion => 1;
198
199          coerce PathTiny,
200             from InstanceOf["My::File"], via { $_->get_path };
201
202       An arrayref of strings can now be coerced to an arrayref of Path::Tiny
203       objects, but is it also now possible to coerce an arrayref of My::File
204       objects to an arrayref of Path::Tiny objects?
205
206       Currently the answer is "no", but this is mostly down to implementation
207       details. It's not clear what the best way to behave in this situation
208       is, and it could start working at some point in the future.
209
210       You should avoid falling into this trap by following the advice found
211       under "The (Lack of) Zen of Coercions".
212
213   Chained Coercions
214       Consider the following type library:
215
216          {
217             package Types::Geometric;
218             use Type::Library -base, -declare => qw(
219                VectorArray
220                VectorArray3D
221                Point
222                Point3D
223             );
224             use Type::Utils;
225             use Types::Standard qw( Num Tuple InstanceOf );
226
227             declare VectorArray,
228                as Tuple[Num, Num];
229
230             declare VectorArray3D,
231                as Tuple[Num, Num, Num];
232
233             coerce VectorArray3D,
234                from VectorArray, via {
235                   [ @$_, 0 ];
236                };
237
238             class_type Point, { class => "Point" };
239
240             coerce Point,
241                from VectorArray, via {
242                   Point->new(x => $_->[0], y => $_->[1]);
243                };
244
245             class_type Point3D, { class => "Point3D" };
246
247             coerce Point3D,
248                from VectorArray3D, via {
249                   Point3D->new(x => $_->[0], y => $_->[1], z => $_->[2]);
250                },
251                from Point, via {
252                   Point3D->new(x => $_->x, y => $_->y, z => 0);
253                };
254          }
255
256       Given an arrayref "[1, 1]" you might reasonably expect it to be
257       coercible to a "Point3D" object; it matches the type constraint
258       "VectorArray" so can be coerced to "VectorArray3D" and thus to
259       "Point3D".
260
261       However, Type::Coercion does not automatically chain coercions like
262       this. Firstly, it would be incompatible with Moose's type coercion
263       system which does not chain coercions. Secondly, it's ambiguous; in our
264       example, the arrayref could be coerced along two different paths (via
265       "VectorArray3D" or via "Point"); in this case the end result would be
266       the same, but in other cases it might not. Thirdly, it runs the risk of
267       accidentally creating loops.
268
269       Doing the chaining manually though is pretty simple. Firstly, we'll
270       take note of the "coercibles" method in Type::Tiny. This method called
271       as "VectorArray3D->coercibles" returns a type constraint meaning
272       "anything that can be coerced to a "VectorArray3D"".
273
274       So we can define the coercions for "Point3D" as:
275
276          coerce Point3D,
277             from VectorArray3D->coercibles, via {
278                my $tmp = to_VectorArray3D($_);
279                Point3D->new(x => $tmp->[0], y => $tmp->[1], z => $tmp->[2]);
280             },
281             from Point, via {
282                Point3D->new(x => $_->x, y => $_->y, z => 0);
283             };
284
285       ... and now coercing from "[1, 1]" will work.
286
287   The (Lack of) Zen of Coercions
288       Coercions can lead to ugliness.
289
290       Let's say we define a type constraint "Path" which has a coercion from
291       "Str". Now we define a class which uses that type constraint.
292
293       Now in another class, we define a coercion from "ArrayRef" to "Path".
294       This kind of action at a distance is not really desirable. And in fact,
295       things will probably subtly break - the first class may have already
296       built a constructor inlining a bunch of code from the coercion.
297
298       However, you too can achieve coercion zen by following these three
299       weird tricks
300       <http://www.slate.com/articles/business/moneybox/2013/07/how_one_weird_trick_conquered_the_internet_what_happens_when_you_click_on.html>:
301
302       1.  If you want to define coercions for a type, do it within your type
303           constraint library, so the coercions are all defined before the
304           type constraint is ever used.
305
306       2.  At the end of your type constraint library, consider calling
307           "$type->coercion->freeze" on each type constraint that has a
308           coercion. This makes the type's coercions immutable. If anybody
309           wants to define any additional coercions, they'll have to create a
310           child type to do it with.
311
312           A shortcut exists to do this on all types in your library:
313
314              __PACKAGE__->meta->make_immutable;
315
316       3.  Use "plus_coercions" and similar methods to easily create a child
317           type constraint of any existing type, and add more coercions to it.
318           Don't fiddle directly with the existing type constraint which may
319           be being used elsewhere.
320
321           Note that these methods all return type constraint objects with
322           frozen (immutable) coercions.
323
324       That's it.
325

SEE ALSO

327       Moose::Manual::BestPractices,
328       <http://www.catalyzed.org/2009/06/keeping-your-coercions-to-yourself.html>.
329

AUTHOR

331       Toby Inkster <tobyink@cpan.org>.
332
334       This software is copyright (c) 2013-2014, 2017-2019 by Toby Inkster.
335
336       This is free software; you can redistribute it and/or modify it under
337       the same terms as the Perl 5 programming language system itself.
338

DISCLAIMER OF WARRANTIES

340       THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
341       WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
342       MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
343
344
345
346perl v5.28.1                      2019-01-08  Type::Tiny::Manual::Coercions(3)
Impressum