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