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