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