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