maybe a more concise synopsis
[gitmo/MooseX-Dependent.git] / lib / MooseX / Types / Parameterizable.pm
CommitLineData
ca01e833 1package MooseX::Types::Parameterizable;
88f7dcd2 2
3use 5.008;
4
5our $VERSION = '0.01';
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
1fa27116 20 use Moose;
ca01e833 21 use MooseX::Types::Parameterizable qw(Parameterizable);
1fa27116 22 use MooseX::Types::Moose qw(Str Int);
23 use MooseX::Types -declare=>[qw(Varchar)];
24
25 subtype Varchar,
26 as Parameterizable[Str,Int],
27 where {
28 my($string, $int) = @_;
29 $int >= length($string) ? 1:0;
30 },
31 message {
32 "'$_' is too long"
33 };
34
35 has varchar_five => (isa=>Varchar[5], is=>'ro');
36 has varchar_ten => (isa=>Varchar[10], is=>'ro');
37
38 ## This works fine
39 my $object1 = __PACKAGE__->new(
40 varchar_five => '1234',
41 varchar_ten => '123456789',
42 );
43
44 ## This explodes with a type constraint error
45 my $object2 = __PACKAGE__->new(
46 varchar_five => '12345678', ## Too long string
47 varchar_ten => '123456789',
48 );
49
50See t/05-pod-examples.t for runnable versions of all POD code
51
a018b5bb 52=head1 DESCRIPTION
53
88f7dcd2 54A L<MooseX::Types> library for creating parameterizable types. A parameterizable type
5964b3ca 55constraint for all intents and uses is a subclass of a parent type, but adds a
56secondary type parameter which is available to constraint callbacks (such as
57inside the 'where' clause) or in the coercions.
58
59This allows you to create a type that has additional runtime advice, such as a
60set of numbers within which another number must be unique, or allowable ranges
61for a integer, such as in:
62
9be9bf16 63 subtype Range,
d1cfb043 64 as Dict[max=>Int, min=>Int],
65 where {
66 my ($range) = @_;
67 return $range->{max} > $range->{min};
68 };
9be9bf16 69
70 subtype RangedInt,
d1cfb043 71 as Parameterizable[Int, Range],
72 where {
73 my ($value, $range) = @_;
74 return ($value >= $range->{min} &&
75 $value <= $range->{max});
76 };
77
9be9bf16 78 RangedInt([{min=>10,max=>100}])->check(50); ## OK
79 RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max
80
6c67366e 81This throws a hard Moose exception. You'll need to capture it in an eval or
9be9bf16 82related exception catching system (see L<TryCatch> or <Try::Tiny>.)
6c67366e 83
9be9bf16 84 RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
6c67366e 85
86If you can't accept a hard exception here, you'll need to test the constraining
87values first, as in:
88
9be9bf16 89 my $range = {min=>99, max=>10};
90 if(my $err = Range->validate($range)) {
d1cfb043 91 ## Handle #$err
9be9bf16 92 } else {
d1cfb043 93 RangedInt($range)->check(99);
9be9bf16 94 }
95
88f7dcd2 96Please note that for ArrayRef or HashRef parameterizable type constraints, as in the
5964b3ca 97example above, as a convenience we automatically ref the incoming type
98parameters, so that the above could also be written as:
99
9be9bf16 100 RangedInt([min=>10,max=>100])->check(50); ## OK
101 RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max
102 RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range!
5964b3ca 103
104This is the preferred syntax, as it improve readability and adds to the
105conciseness of your type constraint declarations. An exception wil be thrown if
106your type parameters don't match the required reference type.
107
6c67366e 108Also not that if you 'chain' parameterization results with a method call like:
109
9be9bf16 110 TypeConstraint([$ob])->method;
111
6c67366e 112You need to have the "(...)" around the ArrayRef in the Type Constraint
113parameters. This seems to have something to do with the precendent level of
114"->". Patches or thoughts welcomed. You only need to do this in the above
115case which I imagine is not a very common case.
116
88f7dcd2 117==head2 Subtyping a Parameterizable type constraints
5964b3ca 118
88f7dcd2 119When subclassing a parameterizable type you must be careful to match either the
5964b3ca 120required type parameter type constraint, or if re-parameterizing, the new
121type constraints are a subtype of the parent. For example:
122
9be9bf16 123 subtype RangedInt,
d1cfb043 124 as Parameterizable[Int, Range],
125 where {
126 my ($value, $range) = @_;
127 return ($value >= $range->{min} &&
128 $value =< $range->{max});
129 };
5964b3ca 130
131Example subtype with additional constraints:
132
9be9bf16 133 subtype PositiveRangedInt,
d1cfb043 134 as RangedInt,
135 where {
136 shift >= 0;
137 };
138
88f58fbf 139Or you could have done the following instead:
5964b3ca 140
9be9bf16 141 ## Subtype of Int for positive numbers
142 subtype PositiveInt,
d1cfb043 143 as Int,
144 where {
145 my ($value, $range) = @_;
146 return $value >= 0;
147 };
9be9bf16 148
149 ## subtype Range to re-parameterize Range with subtypes
150 subtype PositiveRange,
d1cfb043 151 as Range[max=>PositiveInt, min=>PositiveInt];
9be9bf16 152
153 ## create subtype via reparameterizing
154 subtype PositiveRangedInt,
d1cfb043 155 as RangedInt[PositiveRange];
5964b3ca 156
88f7dcd2 157Notice how re-parameterizing the parameterizable type 'RangedInt' works slightly
6c67366e 158differently from re-parameterizing 'PositiveRange' Although it initially takes
88f7dcd2 159two type constraint values to declare a parameterizable type, should you wish to
5964b3ca 160later re-parameterize it, you only use a subtype of the second type parameter
88f7dcd2 161(the parameterizable type constraint) since the first type constraint sets the parent
162type for the parameterizable type. In other words, given the example above, a type
163constraint of 'RangedInt' would have a parent of 'Int', not 'Parameterizable' and for
5964b3ca 164all intends and uses you could stick it wherever you'd need an Int.
165
9be9bf16 166 subtype NameAge,
d1cfb043 167 as Tuple[Str, Int];
9be9bf16 168
d1cfb043 169 ## re-parameterized subtypes of NameAge containing a Parameterizable Int
9be9bf16 170 subtype NameBetween18and35Age,
d1cfb043 171 as NameAge[
172 Str,
173 PositiveRangedInt[min=>18,max=>35],
174 ];
5964b3ca 175
88f7dcd2 176One caveat is that you can't stick an unparameterized parameterizable type inside a
5964b3ca 177structure, such as L<MooseX::Types::Structured> since that would require the
88f7dcd2 178ability to convert a 'containing' type constraint into a parameterizable type, which
5964b3ca 179is a capacity we current don't have.
9be9bf16 180
3cfd35fd 181=head2 Coercions
a018b5bb 182
88f7dcd2 183Parameterizable types have some limited support for coercions. Several things must
26cf337e 184be kept in mind. The first is that the coercion targets the type constraint
88f7dcd2 185which is being made parameterizable, Not the parameterizable type. So for example if you
186create a Parameterizable type like:
26cf337e 187
9be9bf16 188 subtype RequiredAgeInYears,
189 as Int;
26cf337e 190
9be9bf16 191 subtype PersonOverAge,
192 as Parameterizable[Person, RequiredAgeInYears]
193 where {
d1cfb043 194 my ($person, $required_years_old) = @_;
195 return $person->years_old > $required_years_old;
9be9bf16 196 }
26cf337e 197
198This would validate the following:
9be9bf16 199
200 my $person = Person->new(age=>35);
201 PersonOverAge([18])->check($person);
202
26cf337e 203You can then apply the following coercion
204
9be9bf16 205 coerce PersonOverAge,
206 from Dict[age=>int],
207 via {Person->new(%$_)},
208 from Int,
209 via {Person->new(age=>$_)};
210
26cf337e 211This coercion would then apply to all the following:
212
9be9bf16 213 PersonOverAge([18])->check(30); ## via the Int coercion
214 PersonOverAge([18])->check({age=>50}); ## via the Dict coercion
26cf337e 215
88f7dcd2 216However, you are not allowed to place coercions on parameterizable types that have
26cf337e 217had their constraining value filled, nor subtypes of such. For example:
218
9be9bf16 219 coerce PersonOverAge[18],
220 from DateTime,
221 via {$_->years};
222
26cf337e 223That would generate a hard exception. This is a limitation for now until I can
224devise a smarter way to cache the generated type constraints. However, I doubt
225it will be a significant limitation, since the general use case is supported.
226
227Lastly, the constraining value is available in the coercion in much the same way
228it is available to the constraint.
229
9be9bf16 230 ## Create a type constraint where a Person must be in the set
231 subtype PersonInSet,
d1cfb043 232 as Parameterizable[Person, PersonSet],
233 where {
234 my ($person, $person_set) = @_;
235 $person_set->find($person);
236 }
9be9bf16 237
238 coerce PersonInSet,
d1cfb043 239 from HashRef,
240 via {
241 my ($hashref, $person_set) = @_;
242 return $person_set->create($hash_ref);
243 };
a018b5bb 244
3cfd35fd 245=head2 Recursion
a018b5bb 246
9be9bf16 247 TBD
a018b5bb 248
3cfd35fd 249=head1 TYPE CONSTRAINTS
a018b5bb 250
3cfd35fd 251This type library defines the following constraints.
a018b5bb 252
88f7dcd2 253=head2 Parameterizable[ParentTypeConstraint, ParameterizableValueTypeConstraint]
a018b5bb 254
5964b3ca 255Create a subtype of ParentTypeConstraint with a dependency on a value that can
88f7dcd2 256pass the ParameterizableValueTypeConstraint. If ParameterizableValueTypeConstraint is empty
5964b3ca 257we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
9b6d2e22 258
5964b3ca 259This creates a type constraint which must be further parameterized at later time
260before it can be used to ->check or ->validate a value. Attempting to do so
261will cause an exception.
a018b5bb 262
263=cut
264
3cfd35fd 265Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
ca01e833 266 MooseX::Meta::TypeConstraint::Parameterizable->new(
267 name => 'MooseX::Types::Parameterizable::Parameterizable',
a588ee00 268 parent => find_type_constraint('Any'),
d1cfb043 269 constraint => sub {1},
9b6d2e22 270 )
3cfd35fd 271);
9b6d2e22 272
3cfd35fd 273=head1 AUTHOR
a018b5bb 274
3cfd35fd 275John Napiorkowski, C<< <jjnapiork@cpan.org> >>
276
277=head1 COPYRIGHT & LICENSE
a018b5bb 278
279This program is free software; you can redistribute it and/or modify
3cfd35fd 280it under the same terms as Perl itself.
a018b5bb 281
282=cut
9b6d2e22 283
a018b5bb 2841;