better error messages
[gitmo/MooseX-Dependent.git] / lib / MooseX / Types / Parameterizable.pm
1 package MooseX::Types::Parameterizable;
2
3 use 5.008;
4
5 our $VERSION   = '0.01';
6 $VERSION = eval $VERSION;
7
8 use Moose::Util::TypeConstraints;
9 use MooseX::Meta::TypeConstraint::Parameterizable;
10 use MooseX::Types -declare => [qw(Parameterizable)];
11
12 =head1 NAME
13
14 MooseX::Types::Parameterizable - Create your own Parameterizable Types.
15
16 =head1 SYNOPSIS
17
18 Within your L<MooseX::Types> declared library module:
19
20     use Set::Scalar;
21     use MooseX::Types::Parameterizable qw(Parameterizable);
22     use MooseX::Types::Moose qw(Int );
23     use MooseX::Types -declare=>[qw(Set UniqueInt PositiveSet)];
24     
25     subtype Set,
26         as class_type("Set::Scalar");
27
28     subtype UniqueInt,
29         as Parameterizable[Int, Set],
30         where {
31             my ($int, $set) = @_;
32             return !$set->has($int);
33         };
34         
35     subtype PositiveSet,
36         as Set,
37         where {
38             my ($set) = @_;
39             return !grep {$_ <0 } $set->members;
40         };
41         
42     subtype PositiveUniqueInt,
43         as UniqueInt[PositiveSet];
44     
45     my $set = Set::Scalar->new(1,2,3);
46
47     UniqueInt([$set])->check(100);  ## Okay, 100 isn't in (1,2,3)
48     UniqueInt([$set])->check(-99);  ## Okay, -99 isn't in (1,2,3)
49     UniqueInt([$set])->check(2);  ## Not OK, 2 is in (1,2,3)
50     
51     PositiveUniqueInt([$set])->check(100);  ## Okay, 100 isn't in (1,2,3)
52     PositiveUniqueInt([$set])->check(-99);  ## Not OK, -99 not Positive Int
53     PositiveUniqueInt([$set])->check(2);  ## Not OK, 2 is in (1,2,3)
54     
55     my $negative_set = Set::Scalar->new(-1,-2,-3);
56     
57     UniqueInt([$negative_set])->check(100);  ## Throws exception
58         
59 =head1 DESCRIPTION
60
61 A L<MooseX::Types> library for creating parameterizable types.  A parameterizable type
62 constraint for all intents and uses is a subclass of a parent type, but adds a
63 secondary type parameter which is available to constraint callbacks (such as
64 inside the 'where' clause) or in the coercions.
65
66 This allows you to create a type that has additional runtime advice, such as a
67 set of numbers within which another number must be unique, or allowable ranges
68 for a integer, such as in:
69
70     subtype Range,
71         as Dict[max=>Int, min=>Int],
72         where {
73             my ($range) = @_;
74             return $range->{max} > $range->{min};
75         };
76
77     subtype RangedInt,
78         as Parameterizable[Int, Range],
79         where {
80             my ($value, $range) = @_;
81             return ($value >= $range->{min} &&
82              $value <= $range->{max});
83         };
84         
85     RangedInt([{min=>10,max=>100}])->check(50); ## OK
86     RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max
87     
88 This throws a hard Moose exception.  You'll need to capture it in an eval or
89 related exception catching system (see L<TryCatch> or <Try::Tiny>.)
90
91     RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
92
93 If you can't accept a hard exception here, you'll need to test the constraining
94 values first, as in:
95
96     my $range = {min=>99, max=>10};
97     if(my $err = Range->validate($range)) {
98         ## Handle #$err
99     } else {
100         RangedInt($range)->check(99);
101     }
102     
103 Please note that for ArrayRef or HashRef parameterizable type constraints, as in the
104 example above, as a convenience we automatically ref the incoming type
105 parameters, so that the above could also be written as:
106
107     RangedInt([min=>10,max=>100])->check(50); ## OK
108     RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max
109     RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range!
110
111 This is the preferred syntax, as it improve readability and adds to the
112 conciseness of your type constraint declarations.  An exception wil be thrown if
113 your type parameters don't match the required reference type.
114
115 Also not that if you 'chain' parameterization results with a method call like:
116
117     TypeConstraint([$ob])->method;
118     
119 You need to have the "(...)" around the ArrayRef in the Type Constraint
120 parameters.  This seems to have something to do with the precendent level of
121 "->".  Patches or thoughts welcomed.  You only need to do this in the above
122 case which I imagine is not a very common case.
123
124 ==head2 Subtyping a Parameterizable type constraints
125
126 When subclassing a parameterizable type you must be careful to match either the
127 required type parameter type constraint, or if re-parameterizing, the new
128 type constraints are a subtype of the parent.  For example:
129
130     subtype RangedInt,
131         as Parameterizable[Int, Range],
132         where {
133             my ($value, $range) = @_;
134             return ($value >= $range->{min} &&
135              $value =< $range->{max});
136         };
137
138 Example subtype with additional constraints:
139
140     subtype PositiveRangedInt,
141         as RangedInt,
142         where {
143             shift >= 0;              
144         };
145         
146 Or you could have done the following instead:
147
148     ## Subtype of Int for positive numbers
149     subtype PositiveInt,
150         as Int,
151         where {
152             my ($value, $range) = @_;
153             return $value >= 0;
154         };
155
156     ## subtype Range to re-parameterize Range with subtypes
157     subtype PositiveRange,
158         as Range[max=>PositiveInt, min=>PositiveInt];
159     
160     ## create subtype via reparameterizing
161     subtype PositiveRangedInt,
162         as RangedInt[PositiveRange];
163
164 Notice how re-parameterizing the parameterizable type 'RangedInt' works slightly
165 differently from re-parameterizing 'PositiveRange'  Although it initially takes
166 two type constraint values to declare a parameterizable type, should you wish to
167 later re-parameterize it, you only use a subtype of the second type parameter
168 (the parameterizable type constraint) since the first type constraint sets the parent
169 type for the parameterizable type.  In other words, given the example above, a type
170 constraint of 'RangedInt' would have a parent of 'Int', not 'Parameterizable' and for
171 all intends and uses you could stick it wherever you'd need an Int.
172
173     subtype NameAge,
174         as Tuple[Str, Int];
175     
176     ## re-parameterized subtypes of NameAge containing a Parameterizable Int    
177     subtype NameBetween18and35Age,
178         as NameAge[
179             Str,
180             PositiveRangedInt[min=>18,max=>35],
181         ];
182
183 One caveat is that you can't stick an unparameterized parameterizable type inside a
184 structure, such as L<MooseX::Types::Structured> since that would require the
185 ability to convert a 'containing' type constraint into a parameterizable type, which
186 is a capacity we current don't have.
187     
188 =head2 Coercions
189
190 Parameterizable types have some limited support for coercions.  Several things must
191 be kept in mind.  The first is that the coercion targets the type constraint
192 which is being made parameterizable, Not the parameterizable type.  So for example if you
193 create a Parameterizable type like:
194
195     subtype RequiredAgeInYears,
196       as Int;
197
198     subtype PersonOverAge,
199       as Parameterizable[Person, RequiredAgeInYears]
200       where {
201         my ($person, $required_years_old) = @_;
202         return $person->years_old > $required_years_old;
203       }
204
205 This would validate the following:
206     
207     my $person = Person->new(age=>35);
208     PersonOverAge([18])->check($person);
209     
210 You can then apply the following coercion
211
212     coerce PersonOverAge,
213       from Dict[age=>int],
214       via {Person->new(%$_)},
215       from Int,
216       via {Person->new(age=>$_)};
217       
218 This coercion would then apply to all the following:
219
220     PersonOverAge([18])->check(30); ## via the Int coercion
221     PersonOverAge([18])->check({age=>50}); ## via the Dict coercion
222
223 However, you are not allowed to place coercions on parameterizable types that have
224 had their constraining value filled, nor subtypes of such.  For example:
225
226     coerce PersonOverAge[18],
227       from DateTime,
228       via {$_->years};
229       
230 That would generate a hard exception.  This is a limitation for now until I can
231 devise a smarter way to cache the generated type constraints.  However, I doubt
232 it will be a significant limitation, since the general use case is supported.
233
234 Lastly, the constraining value is available in the coercion in much the same way
235 it is available to the constraint.
236
237     ## Create a type constraint where a Person must be in the set
238     subtype PersonInSet,
239         as Parameterizable[Person, PersonSet],
240         where {
241             my ($person, $person_set) = @_;
242             $person_set->find($person);
243         }
244
245     coerce PersonInSet,
246         from HashRef,
247         via {
248             my ($hashref, $person_set) = @_;
249             return $person_set->create($hash_ref);
250         };
251
252 =head2 Recursion
253
254     TBD
255
256 =head1 TYPE CONSTRAINTS
257
258 This type library defines the following constraints.
259
260 =head2 Parameterizable[ParentTypeConstraint, ParameterizableValueTypeConstraint]
261
262 Create a subtype of ParentTypeConstraint with a dependency on a value that can
263 pass the ParameterizableValueTypeConstraint. If ParameterizableValueTypeConstraint is empty
264 we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
265
266 This creates a type constraint which must be further parameterized at later time
267 before it can be used to ->check or ->validate a value.  Attempting to do so
268 will cause an exception.
269
270 =cut
271
272 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
273     MooseX::Meta::TypeConstraint::Parameterizable->new(
274         name => 'MooseX::Types::Parameterizable::Parameterizable',
275         parent => find_type_constraint('Any'),
276         constraint => sub {1},
277     )
278 );
279
280 =head1 AUTHOR
281
282 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
283
284 =head1 COPYRIGHT & LICENSE
285
286 This program is free software; you can redistribute it and/or modify
287 it under the same terms as Perl itself.
288
289 =cut
290
291 1;