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