Commit | Line | Data |
5964b3ca |
1 | package MooseX::Dependent::Types; |
a018b5bb |
2 | |
3cfd35fd |
3 | use Moose::Util::TypeConstraints; |
a588ee00 |
4 | use MooseX::Dependent::Meta::TypeConstraint::Dependent; |
5964b3ca |
5 | use MooseX::Types -declare => [qw(Dependent)]; |
3cfd35fd |
6 | |
a018b5bb |
7 | =head1 NAME |
8 | |
5964b3ca |
9 | MooseX::Dependent::Types - L<MooseX::Types> constraints that depend on values. |
a018b5bb |
10 | |
11 | =head1 SYNOPSIS |
12 | |
5964b3ca |
13 | Within your L<MooseX::Types> declared library module: |
3cfd35fd |
14 | |
613e1e97 |
15 | use MooseX::Dependent::Types qw(Dependent); |
16 | |
17 | subtype UniqueID, |
18 | as Dependent[Int, Set], |
19 | where { |
20 | my ($int, $set) = @_; |
21 | return $set->find($int) ? 0:1; |
22 | }; |
6c67366e |
23 | |
a018b5bb |
24 | =head1 DESCRIPTION |
25 | |
5964b3ca |
26 | A L<MooseX::Types> library for creating dependent types. A dependent type |
27 | constraint for all intents and uses is a subclass of a parent type, but adds a |
28 | secondary type parameter which is available to constraint callbacks (such as |
29 | inside the 'where' clause) or in the coercions. |
30 | |
31 | This allows you to create a type that has additional runtime advice, such as a |
32 | set of numbers within which another number must be unique, or allowable ranges |
33 | for a integer, such as in: |
34 | |
35 | subtype Range, |
36 | as Dict[max=>Int, min=>Int], |
37 | where { |
38 | my ($range) = @_; |
39 | return $range->{max} > $range->{min}; |
40 | }; |
41 | |
42 | subtype RangedInt, |
43 | as Dependent[Int, Range], |
44 | where { |
45 | my ($value, $range) = @_; |
46 | return ($value >= $range->{min} && |
6c67366e |
47 | $value <= $range->{max}); |
5964b3ca |
48 | }; |
49 | |
6c67366e |
50 | RangedInt([{min=>10,max=>100}])->check(50); ## OK |
51 | RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max |
52 | |
53 | This throws a hard Moose exception. You'll need to capture it in an eval or |
54 | related exception catching system (see L<Try::Catch>). |
55 | |
56 | RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range! |
57 | |
58 | If you can't accept a hard exception here, you'll need to test the constraining |
59 | values first, as in: |
60 | |
61 | my $range = {min=>99, max=>10}; |
62 | if(my $err = Range->validate($range)) { |
63 | ## Handle #$err |
64 | } else { |
65 | RangedInt($range)->check(99); |
66 | } |
5964b3ca |
67 | |
68 | Please note that for ArrayRef or HashRef dependent type constraints, as in the |
69 | example above, as a convenience we automatically ref the incoming type |
70 | parameters, so that the above could also be written as: |
71 | |
6c67366e |
72 | RangedInt([min=>10,max=>100])->check(50); ## OK |
73 | RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max |
74 | RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range! |
5964b3ca |
75 | |
76 | This is the preferred syntax, as it improve readability and adds to the |
77 | conciseness of your type constraint declarations. An exception wil be thrown if |
78 | your type parameters don't match the required reference type. |
79 | |
6c67366e |
80 | Also not that if you 'chain' parameterization results with a method call like: |
81 | |
82 | TypeConstraint([$ob])->method; |
83 | |
84 | You need to have the "(...)" around the ArrayRef in the Type Constraint |
85 | parameters. This seems to have something to do with the precendent level of |
86 | "->". Patches or thoughts welcomed. You only need to do this in the above |
87 | case which I imagine is not a very common case. |
88 | |
5964b3ca |
89 | ==head2 Subtyping a Dependent type constraints |
90 | |
91 | When subclassing a dependent type you must be careful to match either the |
92 | required type parameter type constraint, or if re-parameterizing, the new |
93 | type constraints are a subtype of the parent. For example: |
94 | |
95 | subtype RangedInt, |
96 | as Dependent[Int, Range], |
97 | where { |
98 | my ($value, $range) = @_; |
99 | return ($value >= $range->{min} && |
100 | $value =< $range->{max}); |
101 | }; |
102 | |
103 | Example subtype with additional constraints: |
104 | |
105 | subtype PositiveRangedInt, |
106 | as RangedInt, |
107 | where { |
108 | shift >= 0; |
109 | }; |
110 | |
111 | Or you could have done the following instead (example of re-paramterizing) |
112 | |
113 | ## Subtype of Int for positive numbers |
114 | subtype PositiveInt, |
115 | as Int, |
116 | where { |
6c67366e |
117 | my ($value, $range) = @_; |
118 | return $value >= 0; |
5964b3ca |
119 | }; |
120 | |
121 | ## subtype Range to re-parameterize Range with subtypes |
66efbe23 |
122 | subtype PositiveRange, |
5964b3ca |
123 | as Range[max=>PositiveInt, min=>PositiveInt]; |
124 | |
125 | ## create subtype via reparameterizing |
126 | subtype PositiveRangedInt, |
66efbe23 |
127 | as RangedInt[PositiveRange]; |
5964b3ca |
128 | |
129 | Notice how re-parameterizing the dependent type 'RangedInt' works slightly |
6c67366e |
130 | differently from re-parameterizing 'PositiveRange' Although it initially takes |
5964b3ca |
131 | two type constraint values to declare a dependent type, should you wish to |
132 | later re-parameterize it, you only use a subtype of the second type parameter |
133 | (the dependent type constraint) since the first type constraint sets the parent |
134 | type for the dependent type. In other words, given the example above, a type |
135 | constraint of 'RangedInt' would have a parent of 'Int', not 'Dependent' and for |
136 | all intends and uses you could stick it wherever you'd need an Int. |
137 | |
138 | subtype NameAge, |
139 | as Tuple[Str, Int]; |
140 | |
141 | ## re-parameterized subtypes of NameAge containing a Dependent Int |
142 | subtype NameBetween18and35Age, |
143 | as NameAge[ |
144 | Str, |
145 | PositiveRangedInt[min=>18,max=>35], |
146 | ]; |
147 | |
148 | One caveat is that you can't stick an unparameterized dependent type inside a |
149 | structure, such as L<MooseX::Types::Structured> since that would require the |
150 | ability to convert a 'containing' type constraint into a dependent type, which |
151 | is a capacity we current don't have. |
152 | |
3cfd35fd |
153 | =head2 Coercions |
a018b5bb |
154 | |
6b2f4f88 |
155 | You can place coercions on dependent types, however you need to pay attention to |
156 | what you are actually coercion, the unparameterized or parameterized constraint. |
157 | |
5964b3ca |
158 | TBD: Need discussion and example of coercions working for both the |
159 | constrainted and dependent type constraint. |
160 | |
161 | subtype OlderThanAge, |
162 | as Dependent[Int, Dict[older_than=>Int]], |
163 | where { |
164 | my ($value, $dict) = @_; |
165 | return $value > $dict->{older_than} ? 1:0; |
166 | }; |
167 | |
168 | Which should work like: |
169 | |
6c67366e |
170 | OlderThanAge([{older_than=>25}])->check(39); ## is OK |
171 | OlderThanAge([older_than=>1])->check(9); ## OK, using reference type inference |
6b2f4f88 |
172 | |
173 | And you can create coercions like: |
174 | |
5964b3ca |
175 | coerce OlderThanAge, |
176 | from Tuple[Int, Int], |
177 | via { |
178 | my ($int, $int); |
179 | return [$int, {older_than=>$int}]; |
180 | }; |
a018b5bb |
181 | |
3cfd35fd |
182 | =head2 Recursion |
a018b5bb |
183 | |
3cfd35fd |
184 | Newer versions of L<MooseX::Types> support recursive type constraints. That is |
185 | you can include a type constraint as a contained type constraint of itself. |
186 | Recursion is support in both the dependent and constraining type constraint. For |
5964b3ca |
187 | example, if we assume an Object hierarchy like Food -> [Grass, Meat] |
188 | |
189 | TODO: DOES THIS EXAMPLE MAKE SENSE? |
190 | |
191 | subtype Food, |
192 | as Dependent[Food, Food], |
193 | where { |
194 | my ($value, $allowed_food_type) = @_; |
195 | return $value->isa($allowed_food_type); |
196 | }; |
197 | |
198 | my $grass = Food::Grass->new; |
199 | my $meat = Food::Meat->new; |
200 | my $vegetarian = Food[$grass]; |
201 | |
202 | $vegetarian->check($grass); ## Grass is the allowed food of a vegetarian |
203 | $vegetarian->check($meat); ## BANG, vegetarian can't eat meat! |
a018b5bb |
204 | |
3cfd35fd |
205 | =head1 TYPE CONSTRAINTS |
a018b5bb |
206 | |
3cfd35fd |
207 | This type library defines the following constraints. |
a018b5bb |
208 | |
5964b3ca |
209 | =head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint] |
a018b5bb |
210 | |
5964b3ca |
211 | Create a subtype of ParentTypeConstraint with a dependency on a value that can |
212 | pass the DependentValueTypeConstraint. If DependentValueTypeConstraint is empty |
213 | we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>). |
9b6d2e22 |
214 | |
5964b3ca |
215 | This creates a type constraint which must be further parameterized at later time |
216 | before it can be used to ->check or ->validate a value. Attempting to do so |
217 | will cause an exception. |
a018b5bb |
218 | |
219 | =cut |
220 | |
3cfd35fd |
221 | Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( |
a588ee00 |
222 | MooseX::Dependent::Meta::TypeConstraint::Dependent->new( |
5964b3ca |
223 | name => 'MooseX::Dependent::Types::Dependent', |
a588ee00 |
224 | parent => find_type_constraint('Any'), |
0a9f5b94 |
225 | constraint => sub {1}, |
9b6d2e22 |
226 | ) |
3cfd35fd |
227 | ); |
9b6d2e22 |
228 | |
3cfd35fd |
229 | =head1 AUTHOR |
a018b5bb |
230 | |
3cfd35fd |
231 | John Napiorkowski, C<< <jjnapiork@cpan.org> >> |
232 | |
233 | =head1 COPYRIGHT & LICENSE |
a018b5bb |
234 | |
235 | This program is free software; you can redistribute it and/or modify |
3cfd35fd |
236 | it under the same terms as Perl itself. |
a018b5bb |
237 | |
238 | =cut |
9b6d2e22 |
239 | |
a018b5bb |
240 | 1; |