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