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