1 package MooseX::Dependent::Types;
3 use Moose::Util::TypeConstraints;
4 use MooseX::Dependent::Meta::TypeConstraint::Dependent;
5 use MooseX::Types -declare => [qw(Dependent)];
9 MooseX::Dependent::Types - L<MooseX::Types> constraints that depend on values.
13 Within your L<MooseX::Types> declared library module:
15 use MooseX::Dependent::Types qw(Dependent);
18 as class_type("Set::Scalar");
21 as Dependent[Int, Set],
24 return !$set->has($int);
31 return !grep {$_ <0 } $set->members;
34 subtype PositiveUniqueInt,
35 as UniqueInt[PositiveSet];
37 my $set = Set::Scalar->new(1,2,3);
39 UniqueInt([$set])->check(100); ## Okay, 100 isn't in (1,2,3)
40 UniqueInt([$set])->check(-99); ## Okay, -99 isn't in (1,2,3)
41 UniqueInt([$set])->check(2); ## Not OK, 2 is in (1,2,3)
43 PositiveUniqueInt([$set])->check(100); ## Okay, 100 isn't in (1,2,3)
44 PositiveUniqueInt([$set])->check(-99); ## Not OK, -99 not Positive Int
45 PositiveUniqueInt([$set])->check(2); ## Not OK, 2 is in (1,2,3)
47 my $negative_set = Set::Scalar->new(-1,-2,-3);
49 UniqueInt([$negative_set])->check(100); ## Throws exception
53 A L<MooseX::Types> library for creating dependent types. A dependent type
54 constraint for all intents and uses is a subclass of a parent type, but adds a
55 secondary type parameter which is available to constraint callbacks (such as
56 inside the 'where' clause) or in the coercions.
58 This allows you to create a type that has additional runtime advice, such as a
59 set of numbers within which another number must be unique, or allowable ranges
60 for a integer, such as in:
63 as Dict[max=>Int, min=>Int],
66 return $range->{max} > $range->{min};
70 as Dependent[Int, Range],
72 my ($value, $range) = @_;
73 return ($value >= $range->{min} &&
74 $value <= $range->{max});
77 RangedInt([{min=>10,max=>100}])->check(50); ## OK
78 RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max
80 This throws a hard Moose exception. You'll need to capture it in an eval or
81 related exception catching system (see L<TryCatch>).
83 RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
85 If you can't accept a hard exception here, you'll need to test the constraining
88 my $range = {min=>99, max=>10};
89 if(my $err = Range->validate($range)) {
92 RangedInt($range)->check(99);
95 Please note that for ArrayRef or HashRef dependent type constraints, as in the
96 example above, as a convenience we automatically ref the incoming type
97 parameters, so that the above could also be written as:
99 RangedInt([min=>10,max=>100])->check(50); ## OK
100 RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max
101 RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range!
103 This is the preferred syntax, as it improve readability and adds to the
104 conciseness of your type constraint declarations. An exception wil be thrown if
105 your type parameters don't match the required reference type.
107 Also not that if you 'chain' parameterization results with a method call like:
109 TypeConstraint([$ob])->method;
111 You need to have the "(...)" around the ArrayRef in the Type Constraint
112 parameters. This seems to have something to do with the precendent level of
113 "->". Patches or thoughts welcomed. You only need to do this in the above
114 case which I imagine is not a very common case.
116 ==head2 Subtyping a Dependent type constraints
118 When subclassing a dependent type you must be careful to match either the
119 required type parameter type constraint, or if re-parameterizing, the new
120 type constraints are a subtype of the parent. For example:
123 as Dependent[Int, Range],
125 my ($value, $range) = @_;
126 return ($value >= $range->{min} &&
127 $value =< $range->{max});
130 Example subtype with additional constraints:
132 subtype PositiveRangedInt,
138 Or you could have done the following instead:
140 ## Subtype of Int for positive numbers
144 my ($value, $range) = @_;
148 ## subtype Range to re-parameterize Range with subtypes
149 subtype PositiveRange,
150 as Range[max=>PositiveInt, min=>PositiveInt];
152 ## create subtype via reparameterizing
153 subtype PositiveRangedInt,
154 as RangedInt[PositiveRange];
156 Notice how re-parameterizing the dependent type 'RangedInt' works slightly
157 differently from re-parameterizing 'PositiveRange' Although it initially takes
158 two type constraint values to declare a dependent type, should you wish to
159 later re-parameterize it, you only use a subtype of the second type parameter
160 (the dependent type constraint) since the first type constraint sets the parent
161 type for the dependent type. In other words, given the example above, a type
162 constraint of 'RangedInt' would have a parent of 'Int', not 'Dependent' and for
163 all intends and uses you could stick it wherever you'd need an Int.
168 ## re-parameterized subtypes of NameAge containing a Dependent Int
169 subtype NameBetween18and35Age,
172 PositiveRangedInt[min=>18,max=>35],
175 One caveat is that you can't stick an unparameterized dependent type inside a
176 structure, such as L<MooseX::Types::Structured> since that would require the
177 ability to convert a 'containing' type constraint into a dependent type, which
178 is a capacity we current don't have.
182 Dependent types have some limited support for coercions. Several things must
183 be kept in mind. The first is that the coercion targets the type constraint
184 which is being made dependent, Not the dependent type. So for example if you
185 create a Dependent type like:
187 subtype RequiredAgeInYears,
190 subtype PersonOverAge,
191 as Dependent[Person, RequiredAgeInYears]
193 my ($person, $required_years_old) = @_;
194 return $person->years_old > $required_years_old;
197 This would validate the following:
199 my $person = Person->new(age=>35);
200 PersonOverAge([18])->check($person);
202 You can then apply the following coercion
204 coerce PersonOverAge,
206 via {Person->new(%$_)},
208 via {Person->new(age=>$_)};
210 This coercion would then apply to all the following:
212 PersonOverAge([18])->check(30); ## via the Int coercion
213 PersonOverAge([18])->check({age=>50}); ## via the Dict coercion
215 However, you are not allowed to place coercions on dependent types that have
216 had their constraining value filled, nor subtypes of such. For example:
218 coerce PersonOverAge[18],
222 That would generate a hard exception. This is a limitation for now until I can
223 devise a smarter way to cache the generated type constraints. However, I doubt
224 it will be a significant limitation, since the general use case is supported.
226 Lastly, the constraining value is available in the coercion in much the same way
227 it is available to the constraint.
229 ## Create a type constraint where a Person must be in the set
231 as Dependent[Person, PersonSet],
233 my ($person, $person_set) = @_;
234 $person_set->find($person);
240 my ($hashref, $person_set) = @_;
241 return $person_set->create($hash_ref);
248 =head1 TYPE CONSTRAINTS
250 This type library defines the following constraints.
252 =head2 Dependent[ParentTypeConstraint, DependentValueTypeConstraint]
254 Create a subtype of ParentTypeConstraint with a dependency on a value that can
255 pass the DependentValueTypeConstraint. If DependentValueTypeConstraint is empty
256 we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
258 This creates a type constraint which must be further parameterized at later time
259 before it can be used to ->check or ->validate a value. Attempting to do so
260 will cause an exception.
264 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
265 MooseX::Dependent::Meta::TypeConstraint::Dependent->new(
266 name => 'MooseX::Dependent::Types::Dependent',
267 parent => find_type_constraint('Any'),
268 constraint => sub {1},
274 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
276 =head1 COPYRIGHT & LICENSE
278 This program is free software; you can redistribute it and/or modify
279 it under the same terms as Perl itself.