more specs for coercions
[gitmo/MooseX-Dependent.git] / lib / MooseX / Dependent / Types.pm
CommitLineData
5964b3ca 1package MooseX::Dependent::Types;
a018b5bb 2
3cfd35fd 3use 5.008;
4
5use Moose::Util::TypeConstraints;
5964b3ca 6use MooseX::Dependent::Meta::TypeConstraint::Parameterizable;
7use MooseX::Types -declare => [qw(Dependent)];
3cfd35fd 8
9our $VERSION = '0.01';
10our $AUTHORITY = 'cpan:JJNAPIORK';
a018b5bb 11
12=head1 NAME
13
5964b3ca 14MooseX::Dependent::Types - L<MooseX::Types> constraints that depend on values.
a018b5bb 15
16=head1 SYNOPSIS
17
5964b3ca 18Within your L<MooseX::Types> declared library module:
3cfd35fd 19
613e1e97 20 use MooseX::Dependent::Types qw(Dependent);
21
22 subtype UniqueID,
23 as Dependent[Int, Set],
24 where {
25 my ($int, $set) = @_;
26 return $set->find($int) ? 0:1;
27 };
a018b5bb 28
29=head1 DESCRIPTION
30
5964b3ca 31A L<MooseX::Types> library for creating dependent types. A dependent type
32constraint for all intents and uses is a subclass of a parent type, but adds a
33secondary type parameter which is available to constraint callbacks (such as
34inside the 'where' clause) or in the coercions.
35
36This allows you to create a type that has additional runtime advice, such as a
37set of numbers within which another number must be unique, or allowable ranges
38for a integer, such as in:
39
40 subtype Range,
41 as Dict[max=>Int, min=>Int],
42 where {
43 my ($range) = @_;
44 return $range->{max} > $range->{min};
45 };
46
47 subtype RangedInt,
48 as Dependent[Int, Range],
49 where {
50 my ($value, $range) = @_;
51 return ($value >= $range->{min} &&
52 $value =< $range->{max});
53 };
54
55 RangedInt[{min=>10,max=>100}]->check(50); ## OK
56 RangedInt[{min=>50, max=>75}]->check(99); ## Not OK, 99 exceeds max
57 RangedInt[{min=>99, max=>10}]->check(10); ## Not OK, not a valid Range!
58
59Please note that for ArrayRef or HashRef dependent type constraints, as in the
60example above, as a convenience we automatically ref the incoming type
61parameters, so that the above could also be written as:
62
63 RangedInt[min=>10,max=>100]->check(50); ## OK
64 RangedInt[min=>50, max=>75]->check(99); ## Not OK, 99 exceeds max
65 RangedInt[min=>99, max=>10]->check(10); ## Not OK, not a valid Range!
66
67This is the preferred syntax, as it improve readability and adds to the
68conciseness of your type constraint declarations. An exception wil be thrown if
69your type parameters don't match the required reference type.
70
71==head2 Subtyping a Dependent type constraints
72
73When subclassing a dependent type you must be careful to match either the
74required type parameter type constraint, or if re-parameterizing, the new
75type constraints are a subtype of the parent. For example:
76
77 subtype RangedInt,
78 as Dependent[Int, Range],
79 where {
80 my ($value, $range) = @_;
81 return ($value >= $range->{min} &&
82 $value =< $range->{max});
83 };
84
85Example subtype with additional constraints:
86
87 subtype PositiveRangedInt,
88 as RangedInt,
89 where {
90 shift >= 0;
91 };
92
93Or you could have done the following instead (example of re-paramterizing)
94
95 ## Subtype of Int for positive numbers
96 subtype PositiveInt,
97 as Int,
98 where {
99 shift >= 0;
100 };
101
102 ## subtype Range to re-parameterize Range with subtypes
103 subtype PositveRange,
104 as Range[max=>PositiveInt, min=>PositiveInt];
105
106 ## create subtype via reparameterizing
107 subtype PositiveRangedInt,
108 as RangedInt[PositveRange];
109
110Notice how re-parameterizing the dependent type 'RangedInt' works slightly
111differently from re-parameterizing 'PositiveRange'? Although it initially takes
112two type constraint values to declare a dependent type, should you wish to
113later re-parameterize it, you only use a subtype of the second type parameter
114(the dependent type constraint) since the first type constraint sets the parent
115type for the dependent type. In other words, given the example above, a type
116constraint of 'RangedInt' would have a parent of 'Int', not 'Dependent' and for
117all intends and uses you could stick it wherever you'd need an Int.
118
119 subtype NameAge,
120 as Tuple[Str, Int];
121
122 ## re-parameterized subtypes of NameAge containing a Dependent Int
123 subtype NameBetween18and35Age,
124 as NameAge[
125 Str,
126 PositiveRangedInt[min=>18,max=>35],
127 ];
128
129One caveat is that you can't stick an unparameterized dependent type inside a
130structure, such as L<MooseX::Types::Structured> since that would require the
131ability to convert a 'containing' type constraint into a dependent type, which
132is a capacity we current don't have.
133
3cfd35fd 134=head2 Coercions
a018b5bb 135
6b2f4f88 136You can place coercions on dependent types, however you need to pay attention to
137what you are actually coercion, the unparameterized or parameterized constraint.
138
5964b3ca 139 TBD: Need discussion and example of coercions working for both the
140 constrainted and dependent type constraint.
141
142 subtype OlderThanAge,
143 as Dependent[Int, Dict[older_than=>Int]],
144 where {
145 my ($value, $dict) = @_;
146 return $value > $dict->{older_than} ? 1:0;
147 };
148
149Which should work like:
150
6b2f4f88 151 OlderThanAge[{older_than=>25}]->check(39); ## is OK
152 OlderThanAge[older_than=>1]->check(9); ## OK, using reference type inference
153
154And you can create coercions like:
155
5964b3ca 156 coerce OlderThanAge,
157 from Tuple[Int, Int],
158 via {
159 my ($int, $int);
160 return [$int, {older_than=>$int}];
161 };
a018b5bb 162
3cfd35fd 163=head2 Recursion
a018b5bb 164
3cfd35fd 165Newer versions of L<MooseX::Types> support recursive type constraints. That is
166you can include a type constraint as a contained type constraint of itself.
167Recursion is support in both the dependent and constraining type constraint. For
5964b3ca 168example, if we assume an Object hierarchy like Food -> [Grass, Meat]
169
170 TODO: DOES THIS EXAMPLE MAKE SENSE?
171
172 subtype Food,
173 as Dependent[Food, Food],
174 where {
175 my ($value, $allowed_food_type) = @_;
176 return $value->isa($allowed_food_type);
177 };
178
179 my $grass = Food::Grass->new;
180 my $meat = Food::Meat->new;
181 my $vegetarian = Food[$grass];
182
183 $vegetarian->check($grass); ## Grass is the allowed food of a vegetarian
184 $vegetarian->check($meat); ## BANG, vegetarian can't eat meat!
a018b5bb 185
3cfd35fd 186=head1 TYPE CONSTRAINTS
a018b5bb 187
3cfd35fd 188This type library defines the following constraints.
a018b5bb 189
5964b3ca 190=head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint]
a018b5bb 191
5964b3ca 192Create a subtype of ParentTypeConstraint with a dependency on a value that can
193pass the DependentValueTypeConstraint. If DependentValueTypeConstraint is empty
194we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
9b6d2e22 195
5964b3ca 196This creates a type constraint which must be further parameterized at later time
197before it can be used to ->check or ->validate a value. Attempting to do so
198will cause an exception.
a018b5bb 199
200=cut
201
3cfd35fd 202Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
5964b3ca 203 MooseX::Dependent::Meta::TypeConstraint::Parameterizable->new(
204 name => 'MooseX::Dependent::Types::Dependent',
9b6d2e22 205 parent => find_type_constraint('ArrayRef'),
206 constraint_generator=> sub {
207 my ($dependent_val, $callback, $constraining_val) = @_;
208 return $callback->($dependent_val, $constraining_val);
209 },
210 )
3cfd35fd 211);
9b6d2e22 212
3cfd35fd 213=head1 AUTHOR
a018b5bb 214
3cfd35fd 215John Napiorkowski, C<< <jjnapiork@cpan.org> >>
216
217=head1 COPYRIGHT & LICENSE
a018b5bb 218
219This program is free software; you can redistribute it and/or modify
3cfd35fd 220it under the same terms as Perl itself.
a018b5bb 221
222=cut
9b6d2e22 223
a018b5bb 2241;