da97bfc54a5d46fd39357863fbb42b62cfb6a3f4
[gitmo/MooseX-Dependent.git] / lib / MooseX / Types / Parameterizable.pm
1 package MooseX::Types::Parameterizable;
2
3 use 5.008;
4
5 our $VERSION   = '0.03';
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.
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 The type parameter must be valid against the type constraint given.  If you pass
108 an invalid value this throws a hard Moose exception.  You'll need to capture it
109 in an eval or related exception catching system (see L<TryCatch> or L<Try::Tiny>.)
110 For example the following would throw a hard error (and not just return false)
111
112     RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
113
114 If you can't accept a hard exception here, you'll need to test the constraining
115 values first, as in:
116
117     my $range = {min=>99, max=>10};
118     if(my $err = Range->validate($range)) {
119         ## Handle #$err
120     } else {
121         RangedInt($range)->check(99);
122     }
123     
124 Please note that for ArrayRef or HashRef parameterizable type constraints, as in the
125 example above, as a convenience we automatically ref the incoming type
126 parameters, so that the above could also be written as:
127
128     RangedInt([min=>10,max=>100])->check(50); ## OK
129     RangedInt([min=>50, max=>75])->check(99); ## Not OK, exceeds max
130     RangedInt([min=>99, max=>10])->check(10); ## Exception, not valid Range
131
132 This is the preferred syntax, as it improve readability and adds to the
133 conciseness of your type constraint declarations.  An exception wil be thrown if
134 your type parameters don't match the required reference type.
135
136 Also note that if you 'chain' parameterization results with a method call like:
137
138     TypeConstraint([$ob])->method;
139     
140 You need to have the "(...)" around the ArrayRef in the Type Constraint
141 parameters.  You can skip the wrapping parenthesis in the most common cases,
142 such as when you use the type constraint in the options section of a L<Moose>
143 attribute declaration, or when defining type libraries.
144
145 =head2 Subtyping a Parameterizable type constraints
146
147 When subclassing a parameterizable type you must be careful to match either the
148 required type parameter type constraint, or if re-parameterizing, the new
149 type constraints are a subtype of the parent.  For example:
150
151     subtype RangedInt,
152         as Parameterizable[Int, Range],
153         where {
154             my ($value, $range) = @_;
155             return ($value >= $range->{min} &&
156              $value =< $range->{max});
157         };
158
159 Example subtype with additional constraints:
160
161     subtype PositiveRangedInt,
162         as RangedInt,
163         where {
164             shift >= 0;              
165         };
166         
167 In this case you'd now have a parameterizable type constraint called which
168 would work like:
169
170     Test::More::ok PositiveRangedInt([{min=>-10, max=>75}])->check(5);
171     Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5);
172
173 Of course the above is somewhat counter-intuitive to the reader, since we have
174 defined our 'RangedInt' in such as way as to let you declare negative ranges.
175 For the moment each type constraint rule is apply without knowledge of any
176 other rule, nor can a rule 'inform' existing rules.  This is a limitation of
177 the current system.  However, you could instead do the following:
178
179
180     ## Subtype of Int for positive numbers
181     subtype PositiveInt,
182         as Int,
183         where {
184             my ($value, $range) = @_;
185             return $value >= 0;
186         };
187
188     ## subtype Range to re-parameterize Range with subtypes
189     subtype PositiveRange,
190         as Range[max=>PositiveInt, min=>PositiveInt];
191     
192     ## create subtype via reparameterizing
193     subtype PositiveRangedInt,
194         as RangedInt[PositiveRange];
195
196 This would constrain values in the same way as the previous type constraint but
197 have the bonus that you'd throw a hard exception if you try to use an incorrect
198 range:
199
200     Test::More::ok PositiveRangedInt([{min=>10, max=>75}])->check(15); ## OK
201     Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5); ## Dies
202
203 Notice how re-parameterizing the parameterizable type 'RangedInt' works slightly
204 differently from re-parameterizing 'PositiveRange'  Although it initially takes
205 two type constraint values to declare a parameterizable type, should you wish to
206 later re-parameterize it, you only use a subtype of the extra type parameter
207 (the parameterizable type constraints) since the first type constraint sets the
208 parent type for the parameterizable type.
209
210 In other words, given the example above, a type constraint of 'RangedInt' would
211 have a parent of 'Int', not 'Parameterizable' and for all intends and uses you 
212 could stick it wherever you'd need an Int.
213     
214 =head2 Coercions
215
216 A type coerction is a rule that allows you to transform one type from one or
217 more other types.  Please see L<Moose::Cookbook::Basics::Recipe5> for an example
218 of type coercions if you are not familiar with the subject.
219
220 L<MooseX::Types::Parameterizable> support type coercions in all the ways you
221 would expect.  In addition, it also supports a limited form of type coercion
222 inheritance.  Generally speaking, type constraints don't inherit coercions since
223 this would rapidly become confusing.  However, since your parameterizable type
224 is intended to become parameterized in order to be useful, we support inheriting
225 from a 'base' parameterizable type constraint to its 'child' parameterized sub
226 types.
227
228 For the purposes of this discussion, a parameterizable type is a subtype created
229 when you say, "as Parameterizable[..." in your sub type declaration.  For example
230
231     subtype Varchar,
232       as Parameterizable[Str, Int],
233       where {
234         my($string, $int) = @_;
235         $int >= length($string) ? 1:0;
236       },
237       message { "'$_' is too long"  };
238
239 This is the </SYNOPSIS> example, which creates a new parameterizable subtype of
240 Str which takes a single type parameter which must be an Int.  This Int is used
241 to constrain the allowed length of the Str value.
242
243 Now, this new sub type, "Varchar", is parameterizable since it can take a type
244 parameter.  We can apply some coercions to it:
245
246     coerce Varchar,
247       from Object,
248       via { "$_"; },  ## stringify the object
249       from ArrayRef,
250       via { join '',@$_ };  ## convert array to string
251
252 This parameterizable subtype, "Varchar" itself is something you'd never use
253 directly to constraint a value.  In other words you'd never do something like:
254
255     has name => (isa=>Varchar, ...)
256
257 You are going to do this:
258
259     has name => (isa=>Varchar[40], ...)
260
261 Which is actually useful.  However, "Varchar[40]" is a parameterized type, it
262 is a subtype of the parameterizable "Varchar" and it inherits coercions from
263 its parent.  This may be a bit surprising to L<Moose> developers, but I believe
264 this is the actual desired behavior.
265
266 You can of course add new coercions to a subtype of a parameterizable type:
267
268     subtype MySpecialVarchar,
269       as Varchar;
270
271     coerce MySpecialVarchar,
272       from ...
273
274 In which case this new parameterizable type would NOT inherit coercions from
275 it's parent parameterizable type (Varchar).  This is done in keeping with how
276 generally speaking L<Moose> type constraints avoid complicated coercion inheritance
277 schemes, however I am open to discussion if there are valid use cases.
278
279 NOTE: One thing you can't do is add a coercion to an already parameterized type.
280 Currently the following would throw a hard error:
281
282     subtype 40CharStr,
283       as Varchar[40];
284
285     coerce 40CharStr, ...  # BANG!
286
287 This limitation is enforced since generally we expect coercions on the parent.
288 However if good use cases arise we may lift this in the future.
289
290 In general we are trying to take a conservative approach that keeps in line with
291 how most L<Moose> authors expect type constraints to work.
292
293 =head2 Recursion
294
295     TBD - Needs a use case... Anyone?
296
297 =head1 TYPE CONSTRAINTS
298
299 This type library defines the following constraints.
300
301 =head2 Parameterizable[ParentTypeConstraint, ParameterizableValueTypeConstraint]
302
303 Create a subtype of ParentTypeConstraint with a dependency on a value that can
304 pass the ParameterizableValueTypeConstraint. If ParameterizableValueTypeConstraint is empty
305 we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
306
307 This creates a type constraint which must be further parameterized at later time
308 before it can be used to ->check or ->validate a value.  Attempting to do so
309 will cause an exception.
310
311 =cut
312
313 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
314     MooseX::Meta::TypeConstraint::Parameterizable->new(
315         name => 'MooseX::Types::Parameterizable::Parameterizable',
316         parent => find_type_constraint('Any'),
317         constraint => sub {1},
318     )
319 );
320
321 =head1 AUTHOR
322
323 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
324
325 =head1 COPYRIGHT & LICENSE
326
327 This program is free software; you can redistribute it and/or modify
328 it under the same terms as Perl itself.
329
330 =cut
331
332 1;