fixed up the coercion stuff, got something that should give us 80%+ what we need
[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         
53 This throws a hard Moose exception.  You'll need to capture it in an eval or
54 related exception catching system (see L<TryCatch>).
55
56         RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
57
58 If you can't accept a hard exception here, you'll need to test the constraining
59 values first, as in:
60
61         my $range = {min=>99, max=>10};
62         if(my $err = Range->validate($range)) {
63                 ## Handle #$err
64         } else {
65                 RangedInt($range)->check(99);
66         }
67         
68 Please note that for ArrayRef or HashRef dependent type constraints, as in the
69 example above, as a convenience we automatically ref the incoming type
70 parameters, so that the above could also be written as:
71
72         RangedInt([min=>10,max=>100])->check(50); ## OK
73         RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max
74         RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range!
75
76 This is the preferred syntax, as it improve readability and adds to the
77 conciseness of your type constraint declarations.  An exception wil be thrown if
78 your type parameters don't match the required reference type.
79
80 Also not that if you 'chain' parameterization results with a method call like:
81
82         TypeConstraint([$ob])->method;
83         
84 You need to have the "(...)" around the ArrayRef in the Type Constraint
85 parameters.  This seems to have something to do with the precendent level of
86 "->".  Patches or thoughts welcomed.  You only need to do this in the above
87 case which I imagine is not a very common case.
88
89 ==head2 Subtyping a Dependent type constraints
90
91 When subclassing a dependent type you must be careful to match either the
92 required type parameter type constraint, or if re-parameterizing, the new
93 type constraints are a subtype of the parent.  For example:
94
95         subtype RangedInt,
96                 as Dependent[Int, Range],
97                 where {
98                         my ($value, $range) = @_;
99                         return ($value >= $range->{min} &&
100                          $value =< $range->{max});
101                 };
102
103 Example subtype with additional constraints:
104
105         subtype PositiveRangedInt,
106                 as RangedInt,
107                 where {
108                         shift >= 0;                     
109                 };
110                 
111 Or you could have done the following instead (example of re-paramterizing)
112
113         ## Subtype of Int for positive numbers
114         subtype PositiveInt,
115                 as Int,
116                 where {
117                         my ($value, $range) = @_;
118                         return $value >= 0;
119                 };
120
121         ## subtype Range to re-parameterize Range with subtypes
122         subtype PositiveRange,
123                 as Range[max=>PositiveInt, min=>PositiveInt];
124         
125         ## create subtype via reparameterizing
126         subtype PositiveRangedInt,
127                 as RangedInt[PositiveRange];
128
129 Notice how re-parameterizing the dependent type 'RangedInt' works slightly
130 differently from re-parameterizing 'PositiveRange'  Although it initially takes
131 two type constraint values to declare a dependent type, should you wish to
132 later re-parameterize it, you only use a subtype of the second type parameter
133 (the dependent type constraint) since the first type constraint sets the parent
134 type for the dependent type.  In other words, given the example above, a type
135 constraint of 'RangedInt' would have a parent of 'Int', not 'Dependent' and for
136 all intends and uses you could stick it wherever you'd need an Int.
137
138         subtype NameAge,
139                 as Tuple[Str, Int];
140         
141         ## re-parameterized subtypes of NameAge containing a Dependent Int      
142         subtype NameBetween18and35Age,
143                 as NameAge[
144                         Str,
145                         PositiveRangedInt[min=>18,max=>35],
146                 ];
147
148 One caveat is that you can't stick an unparameterized dependent type inside a
149 structure, such as L<MooseX::Types::Structured> since that would require the
150 ability to convert a 'containing' type constraint into a dependent type, which
151 is a capacity we current don't have.
152         
153 =head2 Coercions
154
155 Dependent types have some limited support for coercions.  Several things must
156 be kept in mind.  The first is that the coercion targets the type constraint
157 which is being made dependent, Not the dependent type.  So for example if you
158 create a Dependent type like:
159
160         subtype RequiredAgeInYears,
161           as Int;
162
163         subtype PersonOverAge,
164           as Dependent[Person, RequiredAgeInYears]
165           where {
166                 my ($person, $required_years_old) = @_;
167                 return $person->years_old > $required_years_old;
168           }
169
170 This would validate the following:
171         
172         my $person = Person->new(age=>35);
173         PersonOverAge([18])->check($person);
174         
175 You can then apply the following coercion
176
177         coerce PersonOverAge,
178           from Dict[age=>int],
179           via {Person->new(%$_)},
180           from Int,
181           via {Person->new(age=>$_)};
182           
183 This coercion would then apply to all the following:
184
185         PersonOverAge([18])->check(30); ## via the Int coercion
186         PersonOverAge([18])->check({age=>50}); ## via the Dict coercion
187
188 However, you are not allowed to place coercions on dependent types that have
189 had their constraining value filled, nor subtypes of such.  For example:
190
191         coerce PersonOverAge[18],
192           from DateTime,
193           via {$_->years};
194           
195 That would generate a hard exception.  This is a limitation for now until I can
196 devise a smarter way to cache the generated type constraints.  However, I doubt
197 it will be a significant limitation, since the general use case is supported.
198
199 Lastly, the constraining value is available in the coercion in much the same way
200 it is available to the constraint.
201
202         ## Create a type constraint where a Person must be in the set
203         subtype PersonInSet,
204                 as Dependent[Person, PersonSet],
205                 where {
206                         my ($person, $person_set) = @_;
207                         $person_set->find($person);
208                 }
209
210         coerce PersonInSet,
211                 from HashRef,
212                 via {
213                         my ($hashref, $person_set) = @_;
214                         return $person_set->create($hash_ref);
215                 };
216
217 =head2 Recursion
218
219         TBD
220
221 =head1 TYPE CONSTRAINTS
222
223 This type library defines the following constraints.
224
225 =head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint]
226
227 Create a subtype of ParentTypeConstraint with a dependency on a value that can
228 pass the DependentValueTypeConstraint. If DependentValueTypeConstraint is empty
229 we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
230
231 This creates a type constraint which must be further parameterized at later time
232 before it can be used to ->check or ->validate a value.  Attempting to do so
233 will cause an exception.
234
235 =cut
236
237 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
238     MooseX::Dependent::Meta::TypeConstraint::Dependent->new(
239         name => 'MooseX::Dependent::Types::Dependent',
240         parent => find_type_constraint('Any'),
241                 constraint => sub {1},
242     )
243 );
244
245 =head1 AUTHOR
246
247 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
248
249 =head1 COPYRIGHT & LICENSE
250
251 This program is free software; you can redistribute it and/or modify
252 it under the same terms as Perl itself.
253
254 =cut
255
256 1;