1 package MooseX::Types::Parameterizable;
6 $VERSION = eval $VERSION;
8 use Moose::Util::TypeConstraints;
9 use MooseX::Meta::TypeConstraint::Parameterizable;
10 use MooseX::Types -declare => [qw(Parameterizable)];
14 MooseX::Types::Parameterizable - Create your own Parameterizable Types.
18 The follow is example usage.
20 package Test::MooseX::Types::Parameterizable::Synopsis;
23 use MooseX::Types::Parameterizable qw(Parameterizable);
24 use MooseX::Types::Moose qw(Str Int ArrayRef);
25 use MooseX::Types -declare=>[qw(Varchar)];
27 ## Create a type constraint that is a string but parameterizes an integer
28 ## that is used as a maximum length constraint on that string, similar to
29 ## a SQL Varchar database type.
32 as Parameterizable[Str,Int],
34 my($string, $int) = @_;
35 $int >= length($string) ? 1:0;
37 message { "'$_' is too long" };
39 ## Coerce an ArrayRef to a string via concatenation.
44 my ($arrayref, $int) = @_;
48 has 'varchar_five' => (isa=>Varchar[5], is=>'ro', coerce=>1);
49 has 'varchar_ten' => (isa=>Varchar[10], is=>'ro');
51 ## Object created since attributes are valid
52 my $object1 = __PACKAGE__->new(
53 varchar_five => '1234',
54 varchar_ten => '123456789',
57 ## Dies with an invalid constraint for 'varchar_five'
58 my $object2 = __PACKAGE__->new(
59 varchar_five => '12345678', ## too long!
60 varchar_ten => '123456789',
63 ## varchar_five coerces as expected
64 my $object3 = __PACKAGE__->new(
65 varchar_five => [qw/aa bb/], ## coerces to "aabb"
66 varchar_ten => '123456789',
69 See t/05-pod-examples.t for runnable versions of all POD code
73 A L<MooseX::Types> library for creating parameterizable types. A parameterizable
74 type constraint for all intents and uses is a subclass of a parent type, but
75 adds additional type parameters which are available to constraint callbacks
76 (such as inside the 'where' clause of a type constraint definition) or in the
79 If you have L<Moose> experience, you probably are familiar with the builtin
80 parameterizable type constraints 'ArrayRef' and 'HashRef'. This type constraint
81 lets you generate your own versions of parameterized constraints that work
82 similarly. See L<Moose::Util::TypeConstraints> for more.
84 Using this type constraint, you can generate new type constraints that have
85 additional runtime advice, such as being able to specify maximum and minimum
86 values for an Int (integer) type constraint:
89 as Dict[max=>Int, min=>Int],
92 return $range->{max} > $range->{min};
96 as Parameterizable[Int, Range],
98 my ($value, $range) = @_;
99 return ($value >= $range->{min} &&
100 $value <= $range->{max});
103 RangedInt([{min=>10,max=>100}])->check(50); ## OK
104 RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max
106 The type parameter must be valid against the type constraint given. If you pass
107 an invalid value this throws a hard Moose exception. You'll need to capture it
108 in an eval or related exception catching system (see L<TryCatch> or <Try::Tiny>.)
109 For example the following would throw a hard error (and not just return false)
111 RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
113 If you can't accept a hard exception here, you'll need to test the constraining
116 my $range = {min=>99, max=>10};
117 if(my $err = Range->validate($range)) {
120 RangedInt($range)->check(99);
123 Please note that for ArrayRef or HashRef parameterizable type constraints, as in the
124 example above, as a convenience we automatically ref the incoming type
125 parameters, so that the above could also be written as:
127 RangedInt([min=>10,max=>100])->check(50); ## OK
128 RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max
129 RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range!
131 This is the preferred syntax, as it improve readability and adds to the
132 conciseness of your type constraint declarations. An exception wil be thrown if
133 your type parameters don't match the required reference type.
135 Also note that if you 'chain' parameterization results with a method call like:
137 TypeConstraint([$ob])->method;
139 You need to have the "(...)" around the ArrayRef in the Type Constraint
140 parameters. You can skip the wrapping parenthesis in the most common cases,
141 such as when you use the type constraint in the options section of a L<Moose>
142 attribute declaration, or when defining type libraries.
144 ==head2 Subtyping a Parameterizable type constraints
146 When subclassing a parameterizable type you must be careful to match either the
147 required type parameter type constraint, or if re-parameterizing, the new
148 type constraints are a subtype of the parent. For example:
151 as Parameterizable[Int, Range],
153 my ($value, $range) = @_;
154 return ($value >= $range->{min} &&
155 $value =< $range->{max});
158 Example subtype with additional constraints:
160 subtype PositiveRangedInt,
166 In this case you'd now have a parameterizable type constraint called which
169 Test::More::ok PositiveRangedInt([{min=>-10, max=>75}])->check(5);
170 Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5);
172 Of course the above is somewhat counter-intuitive to the reader, since we have
173 defined our 'RangedInt' in such as way as to let you declare negative ranges.
174 For the moment each type constraint rule is apply without knowledge of any
175 other rule, nor can a rule 'inform' existing rules. This is a limitation of
176 the current system. However, you could instead do the following:
179 ## Subtype of Int for positive numbers
183 my ($value, $range) = @_;
187 ## subtype Range to re-parameterize Range with subtypes
188 subtype PositiveRange,
189 as Range[max=>PositiveInt, min=>PositiveInt];
191 ## create subtype via reparameterizing
192 subtype PositiveRangedInt,
193 as RangedInt[PositiveRange];
195 This would constrain values in the same way as the previous type constraint but
196 have the bonus that you'd throw a hard exception if you try to use an incorrect
199 Test::More::ok PositiveRangedInt([{min=>10, max=>75}])->check(15); ## OK
200 Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5); ## Dies
202 Notice how re-parameterizing the parameterizable type 'RangedInt' works slightly
203 differently from re-parameterizing 'PositiveRange' Although it initially takes
204 two type constraint values to declare a parameterizable type, should you wish to
205 later re-parameterize it, you only use a subtype of the second type parameter
206 (the parameterizable type constraint) since the first type constraint sets the parent
207 type for the parameterizable type. In other words, given the example above, a type
208 constraint of 'RangedInt' would have a parent of 'Int', not 'Parameterizable' and for
209 all intends and uses you could stick it wherever you'd need an Int.
214 ## re-parameterized subtypes of NameAge containing a Parameterizable Int
215 subtype NameBetween18and35Age,
218 PositiveRangedInt[min=>18,max=>35],
221 One caveat is that you can't stick an unparameterized parameterizable type inside a
222 structure, such as L<MooseX::Types::Structured> since that would require the
223 ability to convert a 'containing' type constraint into a parameterizable type, which
224 is a capacity we current don't have.
228 Parameterizable types have some limited support for coercions. Several things must
229 be kept in mind. The first is that the coercion targets the type constraint
230 which is being made parameterizable, Not the parameterizable type. So for example if you
231 create a Parameterizable type like:
233 subtype RequiredAgeInYears,
236 subtype PersonOverAge,
237 as Parameterizable[Person, RequiredAgeInYears]
239 my ($person, $required_years_old) = @_;
240 return $person->years_old > $required_years_old;
243 This would validate the following:
245 my $person = Person->new(age=>35);
246 PersonOverAge([18])->check($person);
248 You can then apply the following coercion
250 coerce PersonOverAge,
252 via {Person->new(%$_)},
254 via {Person->new(age=>$_)};
256 This coercion would then apply to all the following:
258 PersonOverAge([18])->check(30); ## via the Int coercion
259 PersonOverAge([18])->check({age=>50}); ## via the Dict coercion
261 However, you are not allowed to place coercions on parameterizable types that have
262 had their constraining value filled, nor subtypes of such. For example:
264 coerce PersonOverAge[18],
268 That would generate a hard exception. This is a limitation for now until I can
269 devise a smarter way to cache the generated type constraints. However, I doubt
270 it will be a significant limitation, since the general use case is supported.
272 Lastly, the constraining value is available in the coercion in much the same way
273 it is available to the constraint.
275 ## Create a type constraint where a Person must be in the set
277 as Parameterizable[Person, PersonSet],
279 my ($person, $person_set) = @_;
280 $person_set->find($person);
286 my ($hashref, $person_set) = @_;
287 return $person_set->create($hash_ref);
292 TBD - Need more tests.
294 =head1 TYPE CONSTRAINTS
296 This type library defines the following constraints.
298 =head2 Parameterizable[ParentTypeConstraint, ParameterizableValueTypeConstraint]
300 Create a subtype of ParentTypeConstraint with a dependency on a value that can
301 pass the ParameterizableValueTypeConstraint. If ParameterizableValueTypeConstraint is empty
302 we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
304 This creates a type constraint which must be further parameterized at later time
305 before it can be used to ->check or ->validate a value. Attempting to do so
306 will cause an exception.
310 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
311 MooseX::Meta::TypeConstraint::Parameterizable->new(
312 name => 'MooseX::Types::Parameterizable::Parameterizable',
313 parent => find_type_constraint('Any'),
314 constraint => sub {1},
320 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
322 =head1 COPYRIGHT & LICENSE
324 This program is free software; you can redistribute it and/or modify
325 it under the same terms as Perl itself.