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