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