e287889b0c34d4dbce7379097194dd03a6ca1437
[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 extra type parameter
206 (the parameterizable type constraints) since the first type constraint sets the
207 parent type for the parameterizable type.
208
209 In other words, given the example above, a type constraint of 'RangedInt' would
210 have a parent of 'Int', not 'Parameterizable' and for all intends and uses you 
211 could stick it wherever you'd need an Int.
212     
213 =head2 Coercions
214
215 A type coerction is a rule that allows you to transform one type from one or
216 more other types.  Please see L<Moose::Cookbook::Basics::Recipe5> for an example
217 of type coercions if you are not familiar with the subject.
218
219 L<MooseX::Types::Parameterizable> support type coercions in all the ways you
220 would expect.  In addition, it also supports a limited form of type coercion
221 inheritance.  Generally speaking, type constraints don't inherit coercions since
222 this would rapidly become confusing.  However, since your parameterizable type
223 is intended to become parameterized in order to be useful, we support inheriting
224 from a 'base' parameterizable type constraint to its 'child' parameterized sub
225 types.
226
227 For the purposes of this discussion, a parameterizable type is a subtype created
228 when you say, "as Parameterizable[..." in your sub type declaration.  For example
229
230     subtype Varchar,
231       as Parameterizable[Str, Int],
232       where {
233         my($string, $int) = @_;
234         $int >= length($string) ? 1:0;
235       },
236       message { "'$_' is too long"  };
237
238 This is the </SYNOPSIS> example, which creates a new parameterizable subtype of
239 Str which takes a single type parameter which must be an Int.  This Int is used
240 to constrain the allowed length of the Str value.
241
242 Now, this new sub type, "Varchar", is parameterizable since it can take a type
243 parameter.  We can apply some coercions to it:
244
245     coerce Varchar,
246       from Object,
247       via { "$_"; },  ## stringify the object
248       from ArrayRef,
249       via { join '',@$_ };  ## convert array to string
250
251 This parameterizable subtype, "Varchar" itself is something you'd never use
252 directly to constraint a value.  In other words you'd never do something like:
253
254     has name => (isa=>Varchar, ...)
255
256 You are going to do this:
257
258     has name => (isa=>Varchar[40], ...)
259
260 Which is actually useful.  However, "Varchar[40]" is a parameterized type, it
261 is a subtype of the parameterizable "Varchar" and it inherits coercions from
262 its parent.  This may be a bit surprising to L<Moose> developers, but I believe
263 this is the actual desired behavior.
264
265 You can of course add new coercions to a subtype of a parameterizable type:
266
267     subtype MySpecialVarchar,
268       as Varchar;
269
270     coerce MySpecialVarchar,
271       from ...
272
273 In which case this new parameterizable type would NOT inherit coercions from
274 it's parent parameterizable type (Varchar).  This is done in keeping with how
275 generally speaking L<Moose> type constraints avoid complicated coercion inheritance
276 schemes, however I am open to discussion if there are valid use cases.
277
278 NOTE: One thing you can't do is add a coercion to an already parameterized type.
279 Currently the following would throw a hard error:
280
281     subtype 40CharStr,
282       as Varchar[40];
283
284     coerce 40CharStr, ...  # BANG!
285
286 This limitation is enforced since generally we expect coercions on the parent.
287 However if good use cases arise we may lift this in the future.
288
289 In general we are trying to take a conservative approach that keeps in line with
290 how most L<Moose> authors expect type constraints to work.
291
292 =head2 Recursion
293
294     TBD - Need more tests.
295
296 =head1 TYPE CONSTRAINTS
297
298 This type library defines the following constraints.
299
300 =head2 Parameterizable[ParentTypeConstraint, ParameterizableValueTypeConstraint]
301
302 Create a subtype of ParentTypeConstraint with a dependency on a value that can
303 pass the ParameterizableValueTypeConstraint. If ParameterizableValueTypeConstraint is empty
304 we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
305
306 This creates a type constraint which must be further parameterized at later time
307 before it can be used to ->check or ->validate a value.  Attempting to do so
308 will cause an exception.
309
310 =cut
311
312 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
313     MooseX::Meta::TypeConstraint::Parameterizable->new(
314         name => 'MooseX::Types::Parameterizable::Parameterizable',
315         parent => find_type_constraint('Any'),
316         constraint => sub {1},
317     )
318 );
319
320 =head1 AUTHOR
321
322 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
323
324 =head1 COPYRIGHT & LICENSE
325
326 This program is free software; you can redistribute it and/or modify
327 it under the same terms as Perl itself.
328
329 =cut
330
331 1;