Commit | Line | Data |
c08b66ad |
1 | NAME |
2 | MooseX::Types::Parameterizable - Create your own Parameterizable Types. |
3 | |
4 | SYNOPSIS |
afdaaf52 |
5 | The follow is example usage. |
c08b66ad |
6 | |
afdaaf52 |
7 | package Test::MooseX::Types::Parameterizable::Synopsis; |
8 | |
9 | use Moose; |
c08b66ad |
10 | use MooseX::Types::Parameterizable qw(Parameterizable); |
afdaaf52 |
11 | use MooseX::Types::Moose qw(Str Int ArrayRef); |
12 | use MooseX::Types -declare=>[qw(Varchar)]; |
13 | |
7fcab9b4 |
14 | Create a type constraint that is similar to SQL Varchar type. |
afdaaf52 |
15 | |
16 | subtype Varchar, |
17 | as Parameterizable[Str,Int], |
18 | where { |
19 | my($string, $int) = @_; |
20 | $int >= length($string) ? 1:0; |
21 | }, |
22 | message { "'$_' is too long" }; |
23 | |
7fcab9b4 |
24 | Coerce an ArrayRef to a string via concatenation. |
25 | |
afdaaf52 |
26 | coerce Varchar, |
27 | from ArrayRef, |
28 | via { |
29 | my ($arrayref, $int) = @_; |
30 | join('', @$arrayref); |
31 | }; |
32 | |
33 | has 'varchar_five' => (isa=>Varchar[5], is=>'ro', coerce=>1); |
34 | has 'varchar_ten' => (isa=>Varchar[10], is=>'ro'); |
7fcab9b4 |
35 | |
36 | Object created since attributes are valid |
37 | |
afdaaf52 |
38 | my $object1 = __PACKAGE__->new( |
39 | varchar_five => '1234', |
40 | varchar_ten => '123456789', |
41 | ); |
42 | |
7fcab9b4 |
43 | Dies with an invalid constraint for 'varchar_five' |
44 | |
afdaaf52 |
45 | my $object2 = __PACKAGE__->new( |
4a786d1f |
46 | varchar_five => '12345678', ## too long! |
afdaaf52 |
47 | varchar_ten => '123456789', |
48 | ); |
49 | |
7fcab9b4 |
50 | varchar_five coerces as expected |
51 | |
afdaaf52 |
52 | my $object3 = __PACKAGE__->new( |
4a786d1f |
53 | varchar_five => [qw/aa bb/], ## coerces to "aabb" |
afdaaf52 |
54 | varchar_ten => '123456789', |
55 | ); |
56 | |
57 | See t/05-pod-examples.t for runnable versions of all POD code |
c08b66ad |
58 | |
59 | DESCRIPTION |
60 | A MooseX::Types library for creating parameterizable types. A |
61 | parameterizable type constraint for all intents and uses is a subclass |
4a786d1f |
62 | of a parent type, but adds additional type parameters which are |
63 | available to constraint callbacks (such as inside the 'where' clause of |
461e0f82 |
64 | a type constraint definition) or in the coercions you define for a given |
65 | type constraint. |
c08b66ad |
66 | |
4a786d1f |
67 | If you have Moose experience, you probably are familiar with the builtin |
68 | parameterizable type constraints 'ArrayRef' and 'HashRef'. This type |
69 | constraint lets you generate your own versions of parameterized |
70 | constraints that work similarly. See Moose::Util::TypeConstraints for |
71 | more. |
72 | |
73 | Using this type constraint, you can generate new type constraints that |
74 | have additional runtime advice, such as being able to specify maximum |
75 | and minimum values for an Int (integer) type constraint: |
c08b66ad |
76 | |
6c0e3459 |
77 | subtype Range, |
78 | as Dict[max=>Int, min=>Int], |
79 | where { |
80 | my ($range) = @_; |
81 | return $range->{max} > $range->{min}; |
82 | }; |
83 | |
84 | subtype RangedInt, |
85 | as Parameterizable[Int, Range], |
86 | where { |
87 | my ($value, $range) = @_; |
88 | return ($value >= $range->{min} && |
89 | $value <= $range->{max}); |
90 | }; |
91 | |
92 | RangedInt([{min=>10,max=>100}])->check(50); ## OK |
7fcab9b4 |
93 | RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, exceeds max |
c08b66ad |
94 | |
461e0f82 |
95 | This is useful since it lets you generate common patterns of type |
96 | constraints rather than build a custom type constraint for all similar |
97 | cases. |
98 | |
99 | The type parameter must be valid against the 'constrainting' type |
100 | constraint used in the Parameterizable condition. If you pass an invalid |
101 | value this throws a hard Moose exception. You'll need to capture it in |
102 | an eval or related exception catching system (see TryCatch or |
103 | Try::Tiny.) |
104 | |
105 | For example the following would throw a hard error (and not just return |
106 | false) |
c08b66ad |
107 | |
6c0e3459 |
108 | RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range! |
c08b66ad |
109 | |
461e0f82 |
110 | In the above case the 'min' value is larger than the 'max', which |
111 | violates the Range constraint. We throw a hard error here since I think |
112 | incorrect type parameters are most likely to be the result of a typo or |
113 | other true error conditions. |
114 | |
115 | If you can't accept a hard exception here, you can either trap it as |
116 | advised above or you need to test the constraining values first, as in: |
c08b66ad |
117 | |
6c0e3459 |
118 | my $range = {min=>99, max=>10}; |
119 | if(my $err = Range->validate($range)) { |
120 | ## Handle #$err |
121 | } else { |
122 | RangedInt($range)->check(99); |
123 | } |
c08b66ad |
124 | |
125 | Please note that for ArrayRef or HashRef parameterizable type |
126 | constraints, as in the example above, as a convenience we automatically |
127 | ref the incoming type parameters, so that the above could also be |
128 | written as: |
129 | |
6c0e3459 |
130 | RangedInt([min=>10,max=>100])->check(50); ## OK |
7fcab9b4 |
131 | RangedInt([min=>50, max=>75])->check(99); ## Not OK, exceeds max |
132 | RangedInt([min=>99, max=>10])->check(10); ## Exception, not valid Range |
c08b66ad |
133 | |
134 | This is the preferred syntax, as it improve readability and adds to the |
461e0f82 |
135 | conciseness of your type constraint declarations. |
c08b66ad |
136 | |
7fcab9b4 |
137 | Also note that if you 'chain' parameterization results with a method |
138 | call like: |
c08b66ad |
139 | |
6c0e3459 |
140 | TypeConstraint([$ob])->method; |
c08b66ad |
141 | |
142 | You need to have the "(...)" around the ArrayRef in the Type Constraint |
7fcab9b4 |
143 | parameters. You can skip the wrapping parenthesis in the most common |
144 | cases, such as when you use the type constraint in the options section |
145 | of a Moose attribute declaration, or when defining type libraries. |
c08b66ad |
146 | |
7fcab9b4 |
147 | Subtyping a Parameterizable type constraints |
c08b66ad |
148 | When subclassing a parameterizable type you must be careful to match |
149 | either the required type parameter type constraint, or if |
150 | re-parameterizing, the new type constraints are a subtype of the parent. |
151 | For example: |
152 | |
6c0e3459 |
153 | subtype RangedInt, |
154 | as Parameterizable[Int, Range], |
155 | where { |
156 | my ($value, $range) = @_; |
157 | return ($value >= $range->{min} && |
158 | $value =< $range->{max}); |
159 | }; |
c08b66ad |
160 | |
161 | Example subtype with additional constraints: |
162 | |
6c0e3459 |
163 | subtype PositiveRangedInt, |
164 | as RangedInt, |
165 | where { |
166 | shift >= 0; |
167 | }; |
c08b66ad |
168 | |
461e0f82 |
169 | In this case you'd now have a parameterizable type constraint which |
170 | would work like: |
7fcab9b4 |
171 | |
172 | Test::More::ok PositiveRangedInt([{min=>-10, max=>75}])->check(5); |
173 | Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5); |
174 | |
175 | Of course the above is somewhat counter-intuitive to the reader, since |
176 | we have defined our 'RangedInt' in such as way as to let you declare |
177 | negative ranges. For the moment each type constraint rule is apply |
178 | without knowledge of any other rule, nor can a rule 'inform' existing |
179 | rules. This is a limitation of the current system. However, you could |
180 | instead do the following: |
c08b66ad |
181 | |
6c0e3459 |
182 | ## Subtype of Int for positive numbers |
183 | subtype PositiveInt, |
184 | as Int, |
185 | where { |
186 | my ($value, $range) = @_; |
187 | return $value >= 0; |
188 | }; |
189 | |
190 | ## subtype Range to re-parameterize Range with subtypes |
191 | subtype PositiveRange, |
192 | as Range[max=>PositiveInt, min=>PositiveInt]; |
193 | |
194 | ## create subtype via reparameterizing |
195 | subtype PositiveRangedInt, |
196 | as RangedInt[PositiveRange]; |
c08b66ad |
197 | |
7fcab9b4 |
198 | This would constrain values in the same way as the previous type |
199 | constraint but have the bonus that you'd throw a hard exception if you |
200 | try to use an incorrect range: |
201 | |
202 | Test::More::ok PositiveRangedInt([{min=>10, max=>75}])->check(15); ## OK |
203 | Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5); ## Dies |
204 | |
c08b66ad |
205 | Notice how re-parameterizing the parameterizable type 'RangedInt' works |
206 | slightly differently from re-parameterizing 'PositiveRange' Although it |
207 | initially takes two type constraint values to declare a parameterizable |
208 | type, should you wish to later re-parameterize it, you only use a |
7fcab9b4 |
209 | subtype of the extra type parameter (the parameterizable type |
210 | constraints) since the first type constraint sets the parent type for |
211 | the parameterizable type. |
c08b66ad |
212 | |
7fcab9b4 |
213 | In other words, given the example above, a type constraint of |
214 | 'RangedInt' would have a parent of 'Int', not 'Parameterizable' and for |
461e0f82 |
215 | all intends and uses you could stick it wherever you'd need an Int. You |
216 | can't change the parent, even to make it a subclass of Int. |
c08b66ad |
217 | |
7fcab9b4 |
218 | Coercions |
461e0f82 |
219 | A type coercion is a rule that allows you to transform one type from one |
220 | or more other types. Please see Moose::Cookbook::Basics::Recipe5 for an |
221 | example of type coercions if you are not familiar with the subject. |
7fcab9b4 |
222 | |
461e0f82 |
223 | MooseX::Types::Parameterizable supports type coercions in all the ways |
7fcab9b4 |
224 | you would expect. In addition, it also supports a limited form of type |
225 | coercion inheritance. Generally speaking, type constraints don't inherit |
226 | coercions since this would rapidly become confusing. However, since your |
227 | parameterizable type is intended to become parameterized in order to be |
228 | useful, we support inheriting from a 'base' parameterizable type |
229 | constraint to its 'child' parameterized sub types. |
230 | |
231 | For the purposes of this discussion, a parameterizable type is a subtype |
232 | created when you say, "as Parameterizable[..." in your sub type |
461e0f82 |
233 | declaration. For example: |
c08b66ad |
234 | |
7fcab9b4 |
235 | subtype Varchar, |
236 | as Parameterizable[Str, Int], |
6c0e3459 |
237 | where { |
7fcab9b4 |
238 | my($string, $int) = @_; |
239 | $int >= length($string) ? 1:0; |
240 | }, |
241 | message { "'$_' is too long" }; |
c08b66ad |
242 | |
461e0f82 |
243 | This is the "SYNOPSIS" example, which creates a new parameterizable |
7fcab9b4 |
244 | subtype of Str which takes a single type parameter which must be an Int. |
245 | This Int is used to constrain the allowed length of the Str value. |
c08b66ad |
246 | |
7fcab9b4 |
247 | Now, this new sub type, "Varchar", is parameterizable since it can take |
248 | a type parameter. We can apply some coercions to it: |
c08b66ad |
249 | |
7fcab9b4 |
250 | coerce Varchar, |
251 | from Object, |
252 | via { "$_"; }, ## stringify the object |
253 | from ArrayRef, |
254 | via { join '',@$_ }; ## convert array to string |
c08b66ad |
255 | |
7fcab9b4 |
256 | This parameterizable subtype, "Varchar" itself is something you'd never |
257 | use directly to constraint a value. In other words you'd never do |
258 | something like: |
c08b66ad |
259 | |
461e0f82 |
260 | has name => (isa=>Varchar, ...); ## Why not just use a Str? |
c08b66ad |
261 | |
7fcab9b4 |
262 | You are going to do this: |
c08b66ad |
263 | |
7fcab9b4 |
264 | has name => (isa=>Varchar[40], ...) |
c08b66ad |
265 | |
7fcab9b4 |
266 | Which is actually useful. However, "Varchar[40]" is a parameterized |
267 | type, it is a subtype of the parameterizable "Varchar" and it inherits |
268 | coercions from its parent. This may be a bit surprising to Moose |
269 | developers, but I believe this is the actual desired behavior. |
c08b66ad |
270 | |
7fcab9b4 |
271 | You can of course add new coercions to a subtype of a parameterizable |
272 | type: |
c08b66ad |
273 | |
7fcab9b4 |
274 | subtype MySpecialVarchar, |
275 | as Varchar; |
c08b66ad |
276 | |
7fcab9b4 |
277 | coerce MySpecialVarchar, |
278 | from ... |
279 | |
280 | In which case this new parameterizable type would NOT inherit coercions |
281 | from it's parent parameterizable type (Varchar). This is done in keeping |
282 | with how generally speaking Moose type constraints avoid complicated |
283 | coercion inheritance schemes, however I am open to discussion if there |
284 | are valid use cases. |
285 | |
286 | NOTE: One thing you can't do is add a coercion to an already |
287 | parameterized type. Currently the following would throw a hard error: |
288 | |
289 | subtype 40CharStr, |
290 | as Varchar[40]; |
291 | |
292 | coerce 40CharStr, ... # BANG! |
293 | |
294 | This limitation is enforced since generally we expect coercions on the |
295 | parent. However if good use cases arise we may lift this in the future. |
296 | |
297 | In general we are trying to take a conservative approach that keeps in |
298 | line with how most Moose authors expect type constraints to work. |
c08b66ad |
299 | |
300 | Recursion |
7fcab9b4 |
301 | TBD - Needs a use case... Anyone? |
c08b66ad |
302 | |
303 | TYPE CONSTRAINTS |
304 | This type library defines the following constraints. |
305 | |
461e0f82 |
306 | Parameterizable[ParentTypeConstraint, ConstrainingValueTypeConstraint] |
c08b66ad |
307 | Create a subtype of ParentTypeConstraint with a dependency on a value |
461e0f82 |
308 | that can pass the ConstrainingValueTypeConstraint. If |
309 | ConstrainingValueTypeConstraint is empty we default to the 'Any' type |
310 | constraint (see Moose::Util::TypeConstraints). This is useful if you are |
311 | creating some base Parameterizable type constraints that you intend to |
312 | sub class. |
313 | |
314 | SEE ALSO |
315 | The following modules or resources may be of interest. |
c08b66ad |
316 | |
461e0f82 |
317 | Moose, Moose::Meta::TypeConstraint, MooseX::Types |
c08b66ad |
318 | |
319 | AUTHOR |
320 | John Napiorkowski, "<jjnapiork@cpan.org>" |
321 | |
322 | COPYRIGHT & LICENSE |
323 | This program is free software; you can redistribute it and/or modify it |
324 | under the same terms as Perl itself. |
325 | |