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 | }; |
a018b5bb |
23 | |
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} && |
47 | $value =< $range->{max}); |
48 | }; |
49 | |
50 | RangedInt[{min=>10,max=>100}]->check(50); ## OK |
51 | RangedInt[{min=>50, max=>75}]->check(99); ## Not OK, 99 exceeds max |
52 | RangedInt[{min=>99, max=>10}]->check(10); ## Not OK, not a valid Range! |
53 | |
54 | Please note that for ArrayRef or HashRef dependent type constraints, as in the |
55 | example above, as a convenience we automatically ref the incoming type |
56 | parameters, so that the above could also be written as: |
57 | |
58 | RangedInt[min=>10,max=>100]->check(50); ## OK |
59 | RangedInt[min=>50, max=>75]->check(99); ## Not OK, 99 exceeds max |
60 | RangedInt[min=>99, max=>10]->check(10); ## Not OK, not a valid Range! |
61 | |
62 | This is the preferred syntax, as it improve readability and adds to the |
63 | conciseness of your type constraint declarations. An exception wil be thrown if |
64 | your type parameters don't match the required reference type. |
65 | |
66 | ==head2 Subtyping a Dependent type constraints |
67 | |
68 | When subclassing a dependent type you must be careful to match either the |
69 | required type parameter type constraint, or if re-parameterizing, the new |
70 | type constraints are a subtype of the parent. For example: |
71 | |
72 | subtype RangedInt, |
73 | as Dependent[Int, Range], |
74 | where { |
75 | my ($value, $range) = @_; |
76 | return ($value >= $range->{min} && |
77 | $value =< $range->{max}); |
78 | }; |
79 | |
80 | Example subtype with additional constraints: |
81 | |
82 | subtype PositiveRangedInt, |
83 | as RangedInt, |
84 | where { |
85 | shift >= 0; |
86 | }; |
87 | |
88 | Or you could have done the following instead (example of re-paramterizing) |
89 | |
90 | ## Subtype of Int for positive numbers |
91 | subtype PositiveInt, |
92 | as Int, |
93 | where { |
94 | shift >= 0; |
95 | }; |
96 | |
97 | ## subtype Range to re-parameterize Range with subtypes |
98 | subtype PositveRange, |
99 | as Range[max=>PositiveInt, min=>PositiveInt]; |
100 | |
101 | ## create subtype via reparameterizing |
102 | subtype PositiveRangedInt, |
103 | as RangedInt[PositveRange]; |
104 | |
105 | Notice how re-parameterizing the dependent type 'RangedInt' works slightly |
106 | differently from re-parameterizing 'PositiveRange'? Although it initially takes |
107 | two type constraint values to declare a dependent type, should you wish to |
108 | later re-parameterize it, you only use a subtype of the second type parameter |
109 | (the dependent type constraint) since the first type constraint sets the parent |
110 | type for the dependent type. In other words, given the example above, a type |
111 | constraint of 'RangedInt' would have a parent of 'Int', not 'Dependent' and for |
112 | all intends and uses you could stick it wherever you'd need an Int. |
113 | |
114 | subtype NameAge, |
115 | as Tuple[Str, Int]; |
116 | |
117 | ## re-parameterized subtypes of NameAge containing a Dependent Int |
118 | subtype NameBetween18and35Age, |
119 | as NameAge[ |
120 | Str, |
121 | PositiveRangedInt[min=>18,max=>35], |
122 | ]; |
123 | |
124 | One caveat is that you can't stick an unparameterized dependent type inside a |
125 | structure, such as L<MooseX::Types::Structured> since that would require the |
126 | ability to convert a 'containing' type constraint into a dependent type, which |
127 | is a capacity we current don't have. |
128 | |
3cfd35fd |
129 | =head2 Coercions |
a018b5bb |
130 | |
6b2f4f88 |
131 | You can place coercions on dependent types, however you need to pay attention to |
132 | what you are actually coercion, the unparameterized or parameterized constraint. |
133 | |
5964b3ca |
134 | TBD: Need discussion and example of coercions working for both the |
135 | constrainted and dependent type constraint. |
136 | |
137 | subtype OlderThanAge, |
138 | as Dependent[Int, Dict[older_than=>Int]], |
139 | where { |
140 | my ($value, $dict) = @_; |
141 | return $value > $dict->{older_than} ? 1:0; |
142 | }; |
143 | |
144 | Which should work like: |
145 | |
6b2f4f88 |
146 | OlderThanAge[{older_than=>25}]->check(39); ## is OK |
147 | OlderThanAge[older_than=>1]->check(9); ## OK, using reference type inference |
148 | |
149 | And you can create coercions like: |
150 | |
5964b3ca |
151 | coerce OlderThanAge, |
152 | from Tuple[Int, Int], |
153 | via { |
154 | my ($int, $int); |
155 | return [$int, {older_than=>$int}]; |
156 | }; |
a018b5bb |
157 | |
3cfd35fd |
158 | =head2 Recursion |
a018b5bb |
159 | |
3cfd35fd |
160 | Newer versions of L<MooseX::Types> support recursive type constraints. That is |
161 | you can include a type constraint as a contained type constraint of itself. |
162 | Recursion is support in both the dependent and constraining type constraint. For |
5964b3ca |
163 | example, if we assume an Object hierarchy like Food -> [Grass, Meat] |
164 | |
165 | TODO: DOES THIS EXAMPLE MAKE SENSE? |
166 | |
167 | subtype Food, |
168 | as Dependent[Food, Food], |
169 | where { |
170 | my ($value, $allowed_food_type) = @_; |
171 | return $value->isa($allowed_food_type); |
172 | }; |
173 | |
174 | my $grass = Food::Grass->new; |
175 | my $meat = Food::Meat->new; |
176 | my $vegetarian = Food[$grass]; |
177 | |
178 | $vegetarian->check($grass); ## Grass is the allowed food of a vegetarian |
179 | $vegetarian->check($meat); ## BANG, vegetarian can't eat meat! |
a018b5bb |
180 | |
3cfd35fd |
181 | =head1 TYPE CONSTRAINTS |
a018b5bb |
182 | |
3cfd35fd |
183 | This type library defines the following constraints. |
a018b5bb |
184 | |
5964b3ca |
185 | =head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint] |
a018b5bb |
186 | |
5964b3ca |
187 | Create a subtype of ParentTypeConstraint with a dependency on a value that can |
188 | pass the DependentValueTypeConstraint. If DependentValueTypeConstraint is empty |
189 | we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>). |
9b6d2e22 |
190 | |
5964b3ca |
191 | This creates a type constraint which must be further parameterized at later time |
192 | before it can be used to ->check or ->validate a value. Attempting to do so |
193 | will cause an exception. |
a018b5bb |
194 | |
195 | =cut |
196 | |
3cfd35fd |
197 | Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( |
a588ee00 |
198 | MooseX::Dependent::Meta::TypeConstraint::Dependent->new( |
5964b3ca |
199 | name => 'MooseX::Dependent::Types::Dependent', |
a588ee00 |
200 | parent => find_type_constraint('Any'), |
9b6d2e22 |
201 | ) |
3cfd35fd |
202 | ); |
9b6d2e22 |
203 | |
3cfd35fd |
204 | =head1 AUTHOR |
a018b5bb |
205 | |
3cfd35fd |
206 | John Napiorkowski, C<< <jjnapiork@cpan.org> >> |
207 | |
208 | =head1 COPYRIGHT & LICENSE |
a018b5bb |
209 | |
210 | This program is free software; you can redistribute it and/or modify |
3cfd35fd |
211 | it under the same terms as Perl itself. |
a018b5bb |
212 | |
213 | =cut |
9b6d2e22 |
214 | |
a018b5bb |
215 | 1; |
a588ee00 |
216 | |
217 | __END__ |
218 | |
219 | oose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( |
220 | Moose::Meta::TypeConstraint::Parameterizable->new( |
221 | name => 'MooseX::Dependent::Types::Dependent', |
222 | parent => find_type_constraint('Any'), |
223 | constraint => sub { 0 }, |
224 | constraint_generator=> sub { |
225 | my ($dependent_val, $callback, $constraining_val) = @_; |
226 | return $callback->($dependent_val, $constraining_val); |
227 | }, |
228 | ) |
229 | ); |
230 | |
231 | |
232 | |
233 | $REGISTRY->add_type_constraint( |
234 | Moose::Meta::TypeConstraint::Parameterizable->new( |
235 | name => 'HashRef', |
236 | package_defined_in => __PACKAGE__, |
237 | parent => find_type_constraint('Ref'), |
238 | constraint => sub { ref($_) eq 'HASH' }, |
239 | optimized => |
240 | \&Moose::Util::TypeConstraints::OptimizedConstraints::HashRef, |
241 | constraint_generator => sub { |
242 | my $type_parameter = shift; |
243 | my $check = $type_parameter->_compiled_type_constraint; |
244 | return sub { |
245 | foreach my $x ( values %$_ ) { |
246 | ( $check->($x) ) || return; |
247 | } |
248 | 1; |
249 | } |
250 | } |
251 | ) |
252 | ); |