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 | |
14 | ## Create a type constraint that is a string but parameterizes an integer |
15 | ## that is used as a maximum length constraint on that string, similar to |
4a786d1f |
16 | ## a SQL Varchar database type. |
afdaaf52 |
17 | |
18 | subtype Varchar, |
19 | as Parameterizable[Str,Int], |
20 | where { |
21 | my($string, $int) = @_; |
22 | $int >= length($string) ? 1:0; |
23 | }, |
24 | message { "'$_' is too long" }; |
25 | |
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'); |
35 | |
36 | ## Object created since attributes are valid |
37 | my $object1 = __PACKAGE__->new( |
38 | varchar_five => '1234', |
39 | varchar_ten => '123456789', |
40 | ); |
41 | |
42 | ## Dies with an invalid constraint for 'varchar_five' |
43 | my $object2 = __PACKAGE__->new( |
4a786d1f |
44 | varchar_five => '12345678', ## too long! |
afdaaf52 |
45 | varchar_ten => '123456789', |
46 | ); |
47 | |
48 | ## varchar_five coerces as expected |
49 | my $object3 = __PACKAGE__->new( |
4a786d1f |
50 | varchar_five => [qw/aa bb/], ## coerces to "aabb" |
afdaaf52 |
51 | varchar_ten => '123456789', |
52 | ); |
53 | |
54 | See t/05-pod-examples.t for runnable versions of all POD code |
c08b66ad |
55 | |
56 | DESCRIPTION |
57 | A MooseX::Types library for creating parameterizable types. A |
58 | parameterizable type constraint for all intents and uses is a subclass |
4a786d1f |
59 | of a parent type, but adds additional type parameters which are |
60 | available to constraint callbacks (such as inside the 'where' clause of |
61 | a type constraint definition) or in the coercions. |
c08b66ad |
62 | |
4a786d1f |
63 | If you have Moose experience, you probably are familiar with the builtin |
64 | parameterizable type constraints 'ArrayRef' and 'HashRef'. This type |
65 | constraint lets you generate your own versions of parameterized |
66 | constraints that work similarly. See Moose::Util::TypeConstraints for |
67 | more. |
68 | |
69 | Using this type constraint, you can generate new type constraints that |
70 | have additional runtime advice, such as being able to specify maximum |
71 | and minimum values for an Int (integer) type constraint: |
c08b66ad |
72 | |
6c0e3459 |
73 | subtype Range, |
74 | as Dict[max=>Int, min=>Int], |
75 | where { |
76 | my ($range) = @_; |
77 | return $range->{max} > $range->{min}; |
78 | }; |
79 | |
80 | subtype RangedInt, |
81 | as Parameterizable[Int, Range], |
82 | where { |
83 | my ($value, $range) = @_; |
84 | return ($value >= $range->{min} && |
85 | $value <= $range->{max}); |
86 | }; |
87 | |
88 | RangedInt([{min=>10,max=>100}])->check(50); ## OK |
89 | RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max |
c08b66ad |
90 | |
4a786d1f |
91 | The type parameter must be valid against the type constraint given. If |
92 | you pass an invalid value this throws a hard Moose exception. You'll |
93 | need to capture it in an eval or related exception catching system (see |
94 | TryCatch or <Try::Tiny>.) For example the following would throw a hard |
95 | error (and not just return false) |
c08b66ad |
96 | |
6c0e3459 |
97 | RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range! |
c08b66ad |
98 | |
99 | If you can't accept a hard exception here, you'll need to test the |
100 | constraining values first, as in: |
101 | |
6c0e3459 |
102 | my $range = {min=>99, max=>10}; |
103 | if(my $err = Range->validate($range)) { |
104 | ## Handle #$err |
105 | } else { |
106 | RangedInt($range)->check(99); |
107 | } |
c08b66ad |
108 | |
109 | Please note that for ArrayRef or HashRef parameterizable type |
110 | constraints, as in the example above, as a convenience we automatically |
111 | ref the incoming type parameters, so that the above could also be |
112 | written as: |
113 | |
6c0e3459 |
114 | RangedInt([min=>10,max=>100])->check(50); ## OK |
115 | RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max |
116 | RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range! |
c08b66ad |
117 | |
118 | This is the preferred syntax, as it improve readability and adds to the |
119 | conciseness of your type constraint declarations. An exception wil be |
120 | thrown if your type parameters don't match the required reference type. |
121 | |
122 | Also not that if you 'chain' parameterization results with a method call |
123 | like: |
124 | |
6c0e3459 |
125 | TypeConstraint([$ob])->method; |
c08b66ad |
126 | |
127 | You need to have the "(...)" around the ArrayRef in the Type Constraint |
128 | parameters. This seems to have something to do with the precendent level |
129 | of "->". Patches or thoughts welcomed. You only need to do this in the |
130 | above case which I imagine is not a very common case. |
131 | |
132 | ==head2 Subtyping a Parameterizable type constraints |
133 | |
134 | When subclassing a parameterizable type you must be careful to match |
135 | either the required type parameter type constraint, or if |
136 | re-parameterizing, the new type constraints are a subtype of the parent. |
137 | For example: |
138 | |
6c0e3459 |
139 | subtype RangedInt, |
140 | as Parameterizable[Int, Range], |
141 | where { |
142 | my ($value, $range) = @_; |
143 | return ($value >= $range->{min} && |
144 | $value =< $range->{max}); |
145 | }; |
c08b66ad |
146 | |
147 | Example subtype with additional constraints: |
148 | |
6c0e3459 |
149 | subtype PositiveRangedInt, |
150 | as RangedInt, |
151 | where { |
152 | shift >= 0; |
153 | }; |
c08b66ad |
154 | |
155 | Or you could have done the following instead: |
156 | |
6c0e3459 |
157 | ## Subtype of Int for positive numbers |
158 | subtype PositiveInt, |
159 | as Int, |
160 | where { |
161 | my ($value, $range) = @_; |
162 | return $value >= 0; |
163 | }; |
164 | |
165 | ## subtype Range to re-parameterize Range with subtypes |
166 | subtype PositiveRange, |
167 | as Range[max=>PositiveInt, min=>PositiveInt]; |
168 | |
169 | ## create subtype via reparameterizing |
170 | subtype PositiveRangedInt, |
171 | as RangedInt[PositiveRange]; |
c08b66ad |
172 | |
173 | Notice how re-parameterizing the parameterizable type 'RangedInt' works |
174 | slightly differently from re-parameterizing 'PositiveRange' Although it |
175 | initially takes two type constraint values to declare a parameterizable |
176 | type, should you wish to later re-parameterize it, you only use a |
177 | subtype of the second type parameter (the parameterizable type |
178 | constraint) since the first type constraint sets the parent type for the |
179 | parameterizable type. In other words, given the example above, a type |
180 | constraint of 'RangedInt' would have a parent of 'Int', not |
181 | 'Parameterizable' and for all intends and uses you could stick it |
182 | wherever you'd need an Int. |
183 | |
6c0e3459 |
184 | subtype NameAge, |
185 | as Tuple[Str, Int]; |
186 | |
187 | ## re-parameterized subtypes of NameAge containing a Parameterizable Int |
188 | subtype NameBetween18and35Age, |
189 | as NameAge[ |
190 | Str, |
191 | PositiveRangedInt[min=>18,max=>35], |
192 | ]; |
c08b66ad |
193 | |
194 | One caveat is that you can't stick an unparameterized parameterizable |
195 | type inside a structure, such as MooseX::Types::Structured since that |
196 | would require the ability to convert a 'containing' type constraint into |
197 | a parameterizable type, which is a capacity we current don't have. |
198 | |
199 | Coercions |
200 | Parameterizable types have some limited support for coercions. Several |
201 | things must be kept in mind. The first is that the coercion targets the |
202 | type constraint which is being made parameterizable, Not the |
203 | parameterizable type. So for example if you create a Parameterizable |
204 | type like: |
205 | |
6c0e3459 |
206 | subtype RequiredAgeInYears, |
207 | as Int; |
c08b66ad |
208 | |
6c0e3459 |
209 | subtype PersonOverAge, |
210 | as Parameterizable[Person, RequiredAgeInYears] |
211 | where { |
212 | my ($person, $required_years_old) = @_; |
213 | return $person->years_old > $required_years_old; |
214 | } |
c08b66ad |
215 | |
216 | This would validate the following: |
217 | |
6c0e3459 |
218 | my $person = Person->new(age=>35); |
219 | PersonOverAge([18])->check($person); |
c08b66ad |
220 | |
221 | You can then apply the following coercion |
222 | |
6c0e3459 |
223 | coerce PersonOverAge, |
224 | from Dict[age=>int], |
225 | via {Person->new(%$_)}, |
226 | from Int, |
227 | via {Person->new(age=>$_)}; |
c08b66ad |
228 | |
229 | This coercion would then apply to all the following: |
230 | |
6c0e3459 |
231 | PersonOverAge([18])->check(30); ## via the Int coercion |
232 | PersonOverAge([18])->check({age=>50}); ## via the Dict coercion |
c08b66ad |
233 | |
234 | However, you are not allowed to place coercions on parameterizable types |
235 | that have had their constraining value filled, nor subtypes of such. For |
236 | example: |
237 | |
6c0e3459 |
238 | coerce PersonOverAge[18], |
239 | from DateTime, |
240 | via {$_->years}; |
c08b66ad |
241 | |
242 | That would generate a hard exception. This is a limitation for now until |
243 | I can devise a smarter way to cache the generated type constraints. |
244 | However, I doubt it will be a significant limitation, since the general |
245 | use case is supported. |
246 | |
247 | Lastly, the constraining value is available in the coercion in much the |
248 | same way it is available to the constraint. |
249 | |
6c0e3459 |
250 | ## Create a type constraint where a Person must be in the set |
251 | subtype PersonInSet, |
252 | as Parameterizable[Person, PersonSet], |
253 | where { |
254 | my ($person, $person_set) = @_; |
255 | $person_set->find($person); |
256 | } |
257 | |
258 | coerce PersonInSet, |
259 | from HashRef, |
260 | via { |
261 | my ($hashref, $person_set) = @_; |
262 | return $person_set->create($hash_ref); |
263 | }; |
c08b66ad |
264 | |
265 | Recursion |
4a786d1f |
266 | TBD - Need more tests. |
c08b66ad |
267 | |
268 | TYPE CONSTRAINTS |
269 | This type library defines the following constraints. |
270 | |
271 | Parameterizable[ParentTypeConstraint, ParameterizableValueTypeConstraint] |
272 | Create a subtype of ParentTypeConstraint with a dependency on a value |
273 | that can pass the ParameterizableValueTypeConstraint. If |
274 | ParameterizableValueTypeConstraint is empty we default to the 'Any' type |
275 | constraint (see Moose::Util::TypeConstraints). |
276 | |
277 | This creates a type constraint which must be further parameterized at |
278 | later time before it can be used to ->check or ->validate a value. |
279 | Attempting to do so will cause an exception. |
280 | |
281 | AUTHOR |
282 | John Napiorkowski, "<jjnapiork@cpan.org>" |
283 | |
284 | COPYRIGHT & LICENSE |
285 | This program is free software; you can redistribute it and/or modify it |
286 | under the same terms as Perl itself. |
287 | |