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