more docs (and learning git checkin process)
[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     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                 };
156
157 =head2 Recursion
158
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
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!
179
180 =head1 TYPE CONSTRAINTS
181
182 This type library defines the following constraints.
183
184 =head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint]
185
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>).
189
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.
193
194 =cut
195
196 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
197     MooseX::Dependent::Meta::TypeConstraint::Parameterizable->new(
198         name => 'MooseX::Dependent::Types::Dependent',
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     )
205 );
206
207 =head1 AUTHOR
208
209 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
210
211 =head1 COPYRIGHT & LICENSE
212
213 This program is free software; you can redistribute it and/or modify
214 it under the same terms as Perl itself.
215
216 =cut
217
218 1;