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