updated readme and doc tweak
[gitmo/MooseX-Dependent.git] / lib / MooseX / Types / Parameterizable.pm
1 package MooseX::Types::Parameterizable;
2
3 use 5.008;
4
5 our $VERSION   = '0.04';
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 similar to SQL Varchar type.
28
29     subtype Varchar,
30       as Parameterizable[Str,Int],
31       where {
32         my($string, $int) = @_;
33         $int >= length($string) ? 1:0;
34       },
35       message { "'$_' is too long"  };
36
37 Coerce an ArrayRef to a string via concatenation.
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
51     my $object1 = __PACKAGE__->new(
52         varchar_five => '1234',
53         varchar_ten => '123456789',
54     );
55
56 Dies with an invalid constraint for 'varchar_five'
57
58     my $object2 = __PACKAGE__->new(
59         varchar_five => '12345678',  ## too long!
60         varchar_ten => '123456789',
61     );
62
63 varchar_five coerces as expected
64
65     my $object3 = __PACKAGE__->new(
66         varchar_five => [qw/aa bb/],  ## coerces to "aabb"
67         varchar_ten => '123456789',
68     );
69  
70 See t/05-pod-examples.t for runnable versions of all POD code
71          
72 =head1 DESCRIPTION
73
74 A L<MooseX::Types> library for creating parameterizable types.  A parameterizable
75 type constraint for all intents and uses is a subclass of a parent type, but 
76 adds additional type parameters which are available to constraint callbacks
77 (such as inside the 'where' clause of a type constraint definition) or in the 
78 coercions you define for a given type constraint.
79
80 If you have L<Moose> experience, you probably are familiar with the builtin 
81 parameterizable type constraints 'ArrayRef' and 'HashRef'.  This type constraint
82 lets you generate your own versions of parameterized constraints that work
83 similarly.  See L<Moose::Util::TypeConstraints> for more.
84
85 Using this type constraint, you can generate new type constraints that have
86 additional runtime advice, such as being able to specify maximum and minimum
87 values for an Int (integer) type constraint:
88
89     subtype Range,
90         as Dict[max=>Int, min=>Int],
91         where {
92             my ($range) = @_;
93             return $range->{max} > $range->{min};
94         };
95
96     subtype RangedInt,
97         as Parameterizable[Int, Range],
98         where {
99             my ($value, $range) = @_;
100             return ($value >= $range->{min} &&
101              $value <= $range->{max});
102         };
103         
104     RangedInt([{min=>10,max=>100}])->check(50); ## OK
105     RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, exceeds max
106
107 This is useful since it lets you generate common patterns of type constraints
108 rather than build a custom type constraint for all similar cases.
109
110 The type parameter must be valid against the 'constrainting' type constraint used
111 in the Parameterizable condition.  If you pass an invalid value this throws a
112 hard Moose exception.  You'll need to capture it in an eval or related exception
113 catching system (see L<TryCatch> or L<Try::Tiny>.)
114
115 For example the following would throw a hard error (and not just return false)
116
117     RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
118
119 In the above case the 'min' value is larger than the 'max', which violates the
120 Range constraint.  We throw a hard error here since I think incorrect type
121 parameters are most likely to be the result of a typo or other true error
122 conditions.  
123
124 If you can't accept a hard exception here, you can either trap it as advised
125 above or you need to test the constraining values first, as in:
126
127     my $range = {min=>99, max=>10};
128     if(my $err = Range->validate($range)) {
129         ## Handle #$err
130     } else {
131         RangedInt($range)->check(99);
132     }
133     
134 Please note that for ArrayRef or HashRef parameterizable type constraints, as in the
135 example above, as a convenience we automatically ref the incoming type
136 parameters, so that the above could also be written as:
137
138     RangedInt([min=>10,max=>100])->check(50); ## OK
139     RangedInt([min=>50, max=>75])->check(99); ## Not OK, exceeds max
140     RangedInt([min=>99, max=>10])->check(10); ## Exception, not valid Range
141
142 This is the preferred syntax, as it improve readability and adds to the
143 conciseness of your type constraint declarations.
144
145 Also note that if you 'chain' parameterization results with a method call like:
146
147     TypeConstraint([$ob])->method;
148     
149 You need to have the "(...)" around the ArrayRef in the Type Constraint
150 parameters.  You can skip the wrapping parenthesis in the most common cases,
151 such as when you use the type constraint in the options section of a L<Moose>
152 attribute declaration, or when defining type libraries.
153
154 =head2 Subtyping a Parameterizable type constraints
155
156 When subclassing a parameterizable type you must be careful to match either the
157 required type parameter type constraint, or if re-parameterizing, the new
158 type constraints are a subtype of the parent.  For example:
159
160     subtype RangedInt,
161         as Parameterizable[Int, Range],
162         where {
163             my ($value, $range) = @_;
164             return ($value >= $range->{min} &&
165              $value =< $range->{max});
166         };
167
168 Example subtype with additional constraints:
169
170     subtype PositiveRangedInt,
171         as RangedInt,
172         where {
173             shift >= 0;              
174         };
175         
176 In this case you'd now have a parameterizable type constraint which would
177 work like:
178
179     Test::More::ok PositiveRangedInt([{min=>-10, max=>75}])->check(5);
180     Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5);
181
182 Of course the above is somewhat counter-intuitive to the reader, since we have
183 defined our 'RangedInt' in such as way as to let you declare negative ranges.
184 For the moment each type constraint rule is apply without knowledge of any
185 other rule, nor can a rule 'inform' existing rules.  This is a limitation of
186 the current system.  However, you could instead do the following:
187
188
189     ## Subtype of Int for positive numbers
190     subtype PositiveInt,
191         as Int,
192         where {
193             my ($value, $range) = @_;
194             return $value >= 0;
195         };
196
197     ## subtype Range to re-parameterize Range with subtypes
198     subtype PositiveRange,
199         as Range[max=>PositiveInt, min=>PositiveInt];
200     
201     ## create subtype via reparameterizing
202     subtype PositiveRangedInt,
203         as RangedInt[PositiveRange];
204
205 This would constrain values in the same way as the previous type constraint but
206 have the bonus that you'd throw a hard exception if you try to use an incorrect
207 range:
208
209     Test::More::ok PositiveRangedInt([{min=>10, max=>75}])->check(15); ## OK
210     Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5); ## Dies
211
212 Notice how re-parameterizing the parameterizable type 'RangedInt' works slightly
213 differently from re-parameterizing 'PositiveRange'  Although it initially takes
214 two type constraint values to declare a parameterizable type, should you wish to
215 later re-parameterize it, you only use a subtype of the extra type parameter
216 (the parameterizable type constraints) since the first type constraint sets the
217 parent type for the parameterizable type.
218
219 In other words, given the example above, a type constraint of 'RangedInt' would
220 have a parent of 'Int', not 'Parameterizable' and for all intends and uses you 
221 could stick it wherever you'd need an Int.  You can't change the parent, even
222 to make it a subclass of Int.
223     
224 =head2 Coercions
225
226 A type coercion is a rule that allows you to transform one type from one or
227 more other types.  Please see L<Moose::Cookbook::Basics::Recipe5> for an example
228 of type coercions if you are not familiar with the subject.
229
230 L<MooseX::Types::Parameterizable> supports type coercions in all the ways you
231 would expect.  In addition, it also supports a limited form of type coercion
232 inheritance.  Generally speaking, type constraints don't inherit coercions since
233 this would rapidly become confusing.  However, since your parameterizable type
234 is intended to become parameterized in order to be useful, we support inheriting
235 from a 'base' parameterizable type constraint to its 'child' parameterized sub
236 types.
237
238 For the purposes of this discussion, a parameterizable type is a subtype created
239 when you say, "as Parameterizable[..." in your sub type declaration.  For
240 example:
241
242     subtype Varchar,
243       as Parameterizable[Str, Int],
244       where {
245         my($string, $int) = @_;
246         $int >= length($string) ? 1:0;
247       },
248       message { "'$_' is too long"  };
249
250 This is the L</SYNOPSIS> example, which creates a new parameterizable subtype of
251 Str which takes a single type parameter which must be an Int.  This Int is used
252 to constrain the allowed length of the Str value.
253
254 Now, this new sub type, "Varchar", is parameterizable since it can take a type
255 parameter.  We can apply some coercions to it:
256
257     coerce Varchar,
258       from Object,
259       via { "$_"; },  ## stringify the object
260       from ArrayRef,
261       via { join '',@$_ };  ## convert array to string
262
263 This parameterizable subtype, "Varchar" itself is something you'd never use
264 directly to constraint a value.  In other words you'd never do something like:
265
266     has name => (isa=>Varchar, ...); ## Why not just use a Str?
267
268 You are going to do this:
269
270     has name => (isa=>Varchar[40], ...)
271
272 Which is actually useful.  However, "Varchar[40]" is a parameterized type, it
273 is a subtype of the parameterizable "Varchar" and it inherits coercions from
274 its parent.  This may be a bit surprising to L<Moose> developers, but I believe
275 this is the actual desired behavior.
276
277 You can of course add new coercions to a subtype of a parameterizable type:
278
279     subtype MySpecialVarchar,
280       as Varchar;
281
282     coerce MySpecialVarchar,
283       from ...
284
285 In which case this new parameterizable type would NOT inherit coercions from
286 it's parent parameterizable type (Varchar).  This is done in keeping with how
287 generally speaking L<Moose> type constraints avoid complicated coercion inheritance
288 schemes, however I am open to discussion if there are valid use cases.
289
290 NOTE: One thing you can't do is add a coercion to an already parameterized type.
291 Currently the following would throw a hard error:
292
293     subtype 40CharStr,
294       as Varchar[40];
295
296     coerce 40CharStr, ...  # BANG!
297
298 This limitation is enforced since generally we expect coercions on the parent.
299 However if good use cases arise we may lift this in the future.
300
301 In general we are trying to take a conservative approach that keeps in line with
302 how most L<Moose> authors expect type constraints to work.
303
304 =head2 Recursion
305
306     TBD - Needs a use case... Anyone?
307
308 =head1 TYPE CONSTRAINTS
309
310 This type library defines the following constraints.
311
312 =head2 Parameterizable[ParentTypeConstraint, ConstrainingValueTypeConstraint]
313
314 Create a subtype of ParentTypeConstraint with a dependency on a value that can
315 pass the ConstrainingValueTypeConstraint. If ConstrainingValueTypeConstraint is empty
316 we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
317 This is useful if you are creating some base Parameterizable type constraints
318 that you intend to sub class.
319
320 =cut
321
322 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
323     MooseX::Meta::TypeConstraint::Parameterizable->new(
324         name => 'MooseX::Types::Parameterizable::Parameterizable',
325         parent => find_type_constraint('Any'),
326         constraint => sub {1},
327     )
328 );
329
330 =head1 SEE ALSO
331
332 The following modules or resources may be of interest.
333
334 L<Moose>, L<Moose::Meta::TypeConstraint>, L<MooseX::Types>
335
336 =head1 AUTHOR
337
338 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
339
340 =head1 COPYRIGHT & LICENSE
341
342 This program is free software; you can redistribute it and/or modify
343 it under the same terms as Perl itself.
344
345 =cut
346
347 1;