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