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