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