more specs for coercions
[gitmo/MooseX-Dependent.git] / lib / MooseX / Dependent / Types.pm
1 package MooseX::Dependent::Types;
2
3 use 5.008;
4
5 use Moose::Util::TypeConstraints;
6 use MooseX::Dependent::Meta::TypeConstraint::Parameterizable;
7 use MooseX::Types -declare => [qw(Dependent)];
8
9 our $VERSION = '0.01';
10 our $AUTHORITY = 'cpan:JJNAPIORK';
11
12 =head1 NAME
13
14 MooseX::Dependent::Types - L<MooseX::Types> constraints that depend on values.
15
16 =head1 SYNOPSIS
17
18 Within your L<MooseX::Types> declared library module:
19
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         };
28
29 =head1 DESCRIPTION
30
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         
134 =head2 Coercions
135
136 You can place coercions on dependent types, however you need to pay attention to
137 what you are actually coercion, the unparameterized or parameterized constraint.
138
139     TBD: Need discussion and example of coercions working for both the
140     constrainted and dependent type constraint.
141         
142         subtype OlderThanAge,
143                 as Dependent[Int, Dict[older_than=>Int]],
144                 where {
145                         my ($value, $dict) = @_;
146                         return $value > $dict->{older_than} ? 1:0;
147                 };
148
149 Which should work like:
150
151         OlderThanAge[{older_than=>25}]->check(39); ## is OK
152         OlderThanAge[older_than=>1]->check(9); ## OK, using reference type inference
153
154 And you can create coercions like:
155
156         coerce OlderThanAge,
157                 from Tuple[Int, Int],
158                 via {
159                         my ($int, $int);
160                         return [$int, {older_than=>$int}];
161                 };
162
163 =head2 Recursion
164
165 Newer versions of L<MooseX::Types> support recursive type constraints.  That is
166 you can include a type constraint as a contained type constraint of itself.
167 Recursion is support in both the dependent and constraining type constraint. For
168 example, if we assume an Object hierarchy like Food -> [Grass, Meat]
169         
170         TODO: DOES THIS EXAMPLE MAKE SENSE?
171         
172     subtype Food,
173                 as Dependent[Food, Food],
174                 where {
175                         my ($value, $allowed_food_type) = @_;
176                         return $value->isa($allowed_food_type);
177                 };
178         
179         my $grass = Food::Grass->new;
180         my $meat = Food::Meat->new;
181         my $vegetarian = Food[$grass];
182         
183         $vegetarian->check($grass); ## Grass is the allowed food of a vegetarian
184         $vegetarian->check($meat); ## BANG, vegetarian can't eat meat!
185
186 =head1 TYPE CONSTRAINTS
187
188 This type library defines the following constraints.
189
190 =head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint]
191
192 Create a subtype of ParentTypeConstraint with a dependency on a value that can
193 pass the DependentValueTypeConstraint. If DependentValueTypeConstraint is empty
194 we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
195
196 This creates a type constraint which must be further parameterized at later time
197 before it can be used to ->check or ->validate a value.  Attempting to do so
198 will cause an exception.
199
200 =cut
201
202 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
203     MooseX::Dependent::Meta::TypeConstraint::Parameterizable->new(
204         name => 'MooseX::Dependent::Types::Dependent',
205         parent => find_type_constraint('ArrayRef'),
206         constraint_generator=> sub { 
207                         my ($dependent_val, $callback, $constraining_val) = @_;
208                         return $callback->($dependent_val, $constraining_val);
209         },
210     )
211 );
212
213 =head1 AUTHOR
214
215 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
216
217 =head1 COPYRIGHT & LICENSE
218
219 This program is free software; you can redistribute it and/or modify
220 it under the same terms as Perl itself.
221
222 =cut
223
224 1;