1Type::Tiny::Manual::CoeUrsceironCso(n3t)ributed Perl DocTuympeen:t:aTtiinoyn::Manual::Coercions(3)
2
3
4
6 Type::Tiny::Manual::Coercions - adding coercions to type constraints
7
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
327 Moose::Manual::BestPractices,
328 <http://www.catalyzed.org/2009/06/keeping-your-coercions-to-yourself.html>.
329
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
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.30.0 2019-07-26 Type::Tiny::Manual::Coercions(3)