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