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