updated makefile requirements and got the basics of coercions in place
[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
6b2f4f88 155You can place coercions on dependent types, however you need to pay attention to
156what you are actually coercion, the unparameterized or parameterized constraint.
157
5964b3ca 158 TBD: Need discussion and example of coercions working for both the
159 constrainted and dependent type constraint.
160
161 subtype OlderThanAge,
162 as Dependent[Int, Dict[older_than=>Int]],
163 where {
164 my ($value, $dict) = @_;
165 return $value > $dict->{older_than} ? 1:0;
166 };
167
168Which should work like:
169
6c67366e 170 OlderThanAge([{older_than=>25}])->check(39); ## is OK
171 OlderThanAge([older_than=>1])->check(9); ## OK, using reference type inference
6b2f4f88 172
173And you can create coercions like:
174
5964b3ca 175 coerce OlderThanAge,
176 from Tuple[Int, Int],
177 via {
178 my ($int, $int);
179 return [$int, {older_than=>$int}];
180 };
a018b5bb 181
3cfd35fd 182=head2 Recursion
a018b5bb 183
3cfd35fd 184Newer versions of L<MooseX::Types> support recursive type constraints. That is
185you can include a type constraint as a contained type constraint of itself.
186Recursion is support in both the dependent and constraining type constraint. For
5964b3ca 187example, if we assume an Object hierarchy like Food -> [Grass, Meat]
188
189 TODO: DOES THIS EXAMPLE MAKE SENSE?
190
191 subtype Food,
192 as Dependent[Food, Food],
193 where {
194 my ($value, $allowed_food_type) = @_;
195 return $value->isa($allowed_food_type);
196 };
197
198 my $grass = Food::Grass->new;
199 my $meat = Food::Meat->new;
200 my $vegetarian = Food[$grass];
201
202 $vegetarian->check($grass); ## Grass is the allowed food of a vegetarian
203 $vegetarian->check($meat); ## BANG, vegetarian can't eat meat!
a018b5bb 204
3cfd35fd 205=head1 TYPE CONSTRAINTS
a018b5bb 206
3cfd35fd 207This type library defines the following constraints.
a018b5bb 208
5964b3ca 209=head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint]
a018b5bb 210
5964b3ca 211Create a subtype of ParentTypeConstraint with a dependency on a value that can
212pass the DependentValueTypeConstraint. If DependentValueTypeConstraint is empty
213we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
9b6d2e22 214
5964b3ca 215This creates a type constraint which must be further parameterized at later time
216before it can be used to ->check or ->validate a value. Attempting to do so
217will cause an exception.
a018b5bb 218
219=cut
220
3cfd35fd 221Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
a588ee00 222 MooseX::Dependent::Meta::TypeConstraint::Dependent->new(
5964b3ca 223 name => 'MooseX::Dependent::Types::Dependent',
a588ee00 224 parent => find_type_constraint('Any'),
0a9f5b94 225 constraint => sub {1},
9b6d2e22 226 )
3cfd35fd 227);
9b6d2e22 228
3cfd35fd 229=head1 AUTHOR
a018b5bb 230
3cfd35fd 231John Napiorkowski, C<< <jjnapiork@cpan.org> >>
232
233=head1 COPYRIGHT & LICENSE
a018b5bb 234
235This program is free software; you can redistribute it and/or modify
3cfd35fd 236it under the same terms as Perl itself.
a018b5bb 237
238=cut
9b6d2e22 239
a018b5bb 2401;