got all the tests working again, and basic type tests in place
[gitmo/MooseX-Dependent.git] / lib / MooseX / Dependent / Types.pm
1 package MooseX::Dependent::Types;
2
3 use Moose::Util::TypeConstraints;
4 use MooseX::Dependent::Meta::TypeConstraint::Dependent;
5 use MooseX::Types -declare => [qw(Dependent)];
6
7 =head1 NAME
8
9 MooseX::Dependent::Types - L<MooseX::Types> constraints that depend on values.
10
11 =head1 SYNOPSIS
12
13 Within your L<MooseX::Types> declared library module:
14
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         };
23
24 =head1 DESCRIPTION
25
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 PositiveRange,
99                 as Range[max=>PositiveInt, min=>PositiveInt];
100         
101         ## create subtype via reparameterizing
102         subtype PositiveRangedInt,
103                 as RangedInt[PositiveRange];
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         
129 =head2 Coercions
130
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
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
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
151         coerce OlderThanAge,
152                 from Tuple[Int, Int],
153                 via {
154                         my ($int, $int);
155                         return [$int, {older_than=>$int}];
156                 };
157
158 =head2 Recursion
159
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
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!
180
181 =head1 TYPE CONSTRAINTS
182
183 This type library defines the following constraints.
184
185 =head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint]
186
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>).
190
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.
194
195 =cut
196
197 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
198     MooseX::Dependent::Meta::TypeConstraint::Dependent->new(
199         name => 'MooseX::Dependent::Types::Dependent',
200         parent => find_type_constraint('Any'),
201                 constraint => sub {1},
202     )
203 );
204
205 =head1 AUTHOR
206
207 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
208
209 =head1 COPYRIGHT & LICENSE
210
211 This program is free software; you can redistribute it and/or modify
212 it under the same terms as Perl itself.
213
214 =cut
215
216 1;
217
218 __END__
219
220 oose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
221     Moose::Meta::TypeConstraint::Parameterizable->new(
222         name => 'MooseX::Dependent::Types::Dependent',
223         parent => find_type_constraint('Any'),
224                 constraint => sub { 0 },
225         constraint_generator=> sub { 
226                         my ($dependent_val, $callback, $constraining_val) = @_;
227                         return $callback->($dependent_val, $constraining_val);
228         },
229     )
230 );
231
232
233
234 $REGISTRY->add_type_constraint(
235     Moose::Meta::TypeConstraint::Parameterizable->new(
236         name               => 'HashRef',
237         package_defined_in => __PACKAGE__,
238         parent             => find_type_constraint('Ref'),
239         constraint         => sub { ref($_) eq 'HASH' },
240         optimized =>
241             \&Moose::Util::TypeConstraints::OptimizedConstraints::HashRef,
242         constraint_generator => sub {
243             my $type_parameter = shift;
244             my $check          = $type_parameter->_compiled_type_constraint;
245             return sub {
246                 foreach my $x ( values %$_ ) {
247                     ( $check->($x) ) || return;
248                 }
249                 1;
250                 }
251         }
252     )
253 );