File Coverage

blib/lib/MooseX/Types/Parameterizable.pm
Criterion Covered Total %
statement 11 11 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 15 15 100.0


line stmt bran cond sub pod time code
1              
2             use 5.008;
3 10     10   3700721  
  10         135  
4             our $VERSION = '0.10';
5             $VERSION = eval $VERSION;
6              
7             use Moose::Util::TypeConstraints;
8 10     10   3787 use MooseX::Meta::TypeConstraint::Parameterizable;
  10         1844509  
  10         104  
9 10     10   28702 use MooseX::Types -declare => [qw(Parameterizable)];
  10         5584  
  10         527  
10 10     10   5026  
  10         392834  
  10         73  
11             Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
12             MooseX::Meta::TypeConstraint::Parameterizable->new(
13             name => 'MooseX::Types::Parameterizable::Parameterizable',
14             parent => find_type_constraint('Any'),
15             constraint => sub {1},
16             )
17             );
18              
19             1;
20              
21              
22             =head1 NAME
23              
24             MooseX::Types::Parameterizable - Create your own Parameterizable Types.
25              
26             =head1 SYNOPSIS
27              
28             The follow is example usage.
29              
30             package Test::MooseX::Types::Parameterizable::Synopsis;
31              
32             use Moose;
33             use MooseX::Types::Parameterizable qw(Parameterizable);
34             use MooseX::Types::Moose qw(Str Int ArrayRef);
35             use MooseX::Types -declare=>[qw(Varchar)];
36              
37             Create a type constraint that is similar to SQL Varchar type.
38              
39             subtype Varchar,
40             as Parameterizable[Str,Int],
41             where {
42             my($string, $int) = @_;
43             $int >= length($string) ? 1:0;
44             },
45             message { "'$_[0]' is too long (max length $_[1])" };
46              
47             Coerce an ArrayRef to a string via concatenation.
48              
49             coerce Varchar,
50             from ArrayRef,
51             via {
52             my ($arrayref, $int) = @_;
53             join('', @$arrayref);
54             };
55              
56             has 'varchar_five' => (isa=>Varchar[5], is=>'ro', coerce=>1);
57              
58             has 'varchar_ten' => (isa=>Varchar[10], is=>'ro');
59              
60             Object created since attributes are valid
61              
62             my $object1 = __PACKAGE__->new(
63             varchar_five => '1234',
64             varchar_ten => '123456789',
65             );
66              
67             Dies with an invalid constraint for 'varchar_five'
68              
69             my $object2 = __PACKAGE__->new(
70             varchar_five => '12345678', ## too long!
71             varchar_ten => '123456789',
72             );
73              
74             varchar_five coerces as expected
75              
76             my $object3 = __PACKAGE__->new(
77             varchar_five => [qw/aa bb/], ## coerces to "aabb"
78             varchar_ten => '123456789',
79             );
80              
81             See t/05-pod-examples.t for runnable versions of all POD code
82              
83             =head1 VERSION 0.09 RELEASE NOTES
84              
85             In trying to solve a critical incompatibility with newer versions of L<Moose>
86             I've had to increase the minimum L<Moose> and L<MooseX::Types> required
87             versions dramatically. I just had no good option to maintain L<Moose> version
88             back compatibility down to VERSION 1.08 (the version of L<Moose> when I first
89             released this package). Please test carefully before upgrading your production
90             code with this version!
91              
92             Options and code are welcomed to help with this. Thanks!
93              
94             B<Update from 2022>: Its been ten years and we never figured out all these issues that
95             arose from Moose 2.0. I got all the tests to pass except for some issues around
96             parameterized types and coercions. In the interests of not having failed code
97             on CPAN I'm releasing all the modern updates and skipping that one block of tests.
98             However I recommend considering this an interesting proof of concept and building
99             a new implementation from start, probably based on L<Type::Tiny> which also works
100             with L<Moo>.
101              
102             As a result I recomend not using this module for new code.
103              
104             =head1 DESCRIPTION
105              
106             A L<MooseX::Types> library for creating parameterizable types. A parameterizable
107             type constraint for all intents and uses is a subclass of a parent type, but
108             adds additional type parameters which are available to constraint callbacks
109             (such as inside the 'where' clause of a type constraint definition) or in the
110             coercions you define for a given type constraint.
111              
112             If you have L<Moose> experience, you probably are familiar with the builtin
113             parameterizable type constraints 'ArrayRef' and 'HashRef'. This type constraint
114             lets you generate your own versions of parameterized constraints that work
115             similarly. See L<Moose::Util::TypeConstraints> for more.
116              
117             Using this type constraint, you can generate new type constraints that have
118             additional runtime advice, such as being able to specify maximum and minimum
119             values for an Int (integer) type constraint:
120              
121             subtype Range,
122             as Dict[max=>Int, min=>Int],
123             where {
124             my ($range) = @_;
125             return $range->{max} > $range->{min};
126             };
127              
128             subtype RangedInt,
129             as Parameterizable[Int, Range],
130             where {
131             my ($value, $range) = @_;
132             return ($value >= $range->{min} &&
133             $value <= $range->{max});
134             },
135             message {
136             my ($value, $range) = @_;
137             return "$value must be between $range->{min} and $range->{max} (inclusive)";
138             };
139              
140             RangedInt([{min=>10,max=>100}])->check(50); ## OK
141             RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, exceeds max
142              
143             This is useful since it lets you generate common patterns of type constraints
144             rather than build a custom type constraint for all similar cases.
145              
146             The type parameter must be valid against the 'constrainting' type constraint used
147             in the Parameterizable condition. If you pass an invalid value this throws a
148             hard Moose exception. You'll need to capture it in an eval or related exception
149             catching system (see L<TryCatch> or L<Try::Tiny>.)
150              
151             For example the following would throw a hard error (and not just return false)
152              
153             RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
154              
155             In the above case the 'min' value is larger than the 'max', which violates the
156             Range constraint. We throw a hard error here since I think incorrect type
157             parameters are most likely to be the result of a typo or other true error
158             conditions.
159              
160             If you can't accept a hard exception here, you can either trap it as advised
161             above or you need to test the constraining values first, as in:
162              
163             my $range = {min=>99, max=>10};
164             if(my $err = Range->validate($range)) {
165             ## Handle #$err
166             } else {
167             RangedInt($range)->check(99);
168             }
169              
170             Please note that for ArrayRef or HashRef parameterizable type constraints, as in the
171             example above, as a convenience we automatically ref the incoming type
172             parameters, so that the above could also be written as:
173              
174             RangedInt([min=>10,max=>100])->check(50); ## OK
175             RangedInt([min=>50, max=>75])->check(99); ## Not OK, exceeds max
176             RangedInt([min=>99, max=>10])->check(10); ## Exception, not valid Range
177              
178             This is the preferred syntax, as it improve readability and adds to the
179             conciseness of your type constraint declarations.
180              
181             Also note that if you 'chain' parameterization results with a method call like:
182              
183             TypeConstraint([$ob])->method;
184              
185             You need to have the "(...)" around the ArrayRef in the Type Constraint
186             parameters. You can skip the wrapping parenthesis in the most common cases,
187             such as when you use the type constraint in the options section of a L<Moose>
188             attribute declaration, or when defining type libraries.
189              
190             =head2 Subtyping a Parameterizable type constraints
191              
192             When subclassing a parameterizable type you must be careful to match either the
193             required type parameter type constraint, or if re-parameterizing, the new
194             type constraints are a subtype of the parent. For example:
195              
196             subtype RangedInt,
197             as Parameterizable[Int, Range],
198             where {
199             my ($value, $range) = @_;
200             return ($value >= $range->{min} &&
201             $value =< $range->{max});
202             },
203             message {
204             my ($value, $range) = @_;
205             return "$value must be between $range->{min} and $range->{max} (inclusive)";
206             };
207              
208             Example subtype with additional constraints:
209              
210             subtype PositiveRangedInt,
211             as RangedInt,
212             where {
213             shift >= 0;
214             };
215              
216             In this case you'd now have a parameterizable type constraint which would
217             work like:
218              
219             Test::More::ok PositiveRangedInt([{min=>-10, max=>75}])->check(5);
220             Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5);
221              
222             Of course the above is somewhat counter-intuitive to the reader, since we have
223             defined our 'RangedInt' in such as way as to let you declare negative ranges.
224             For the moment each type constraint rule is apply without knowledge of any
225             other rule, nor can a rule 'inform' existing rules. This is a limitation of
226             the current system. However, you could instead do the following:
227              
228             ## Subtype of Int for positive numbers
229             subtype PositiveInt,
230             as Int,
231             where {
232             my ($value, $range) = @_;
233             return $value >= 0;
234             };
235              
236             ## subtype Range to re-parameterize Range with subtypes
237             subtype PositiveRange,
238             as Range[max=>PositiveInt, min=>PositiveInt];
239              
240             ## create subtype via reparameterizing
241             subtype PositiveRangedInt,
242             as RangedInt[PositiveRange];
243              
244             This would constrain values in the same way as the previous type constraint but
245             have the bonus that you'd throw a hard exception if you try to use an incorrect
246             range:
247              
248             Test::More::ok PositiveRangedInt([{min=>10, max=>75}])->check(15); ## OK
249             Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5); ## Dies
250              
251             Notice how re-parameterizing the parameterizable type 'RangedInt' works slightly
252             differently from re-parameterizing 'PositiveRange' Although it initially takes
253             two type constraint values to declare a parameterizable type, should you wish to
254             later re-parameterize it, you only use a subtype of the extra type parameter
255             (the parameterizable type constraints) since the first type constraint sets the
256             parent type for the parameterizable type.
257              
258             In other words, given the example above, a type constraint of 'RangedInt' would
259             have a parent of 'Int', not 'Parameterizable' and for all intends and uses you
260             could stick it wherever you'd need an Int. You can't change the parent, even
261             to make it a subclass of Int.
262              
263             =head2 Coercions
264              
265             A type coercion is a rule that allows you to transform one type from one or
266             more other types. Please see L<Moose::Cookbook::Basics::Recipe5> for an example
267             of type coercions if you are not familiar with the subject.
268              
269             L<MooseX::Types::Parameterizable> supports type coercions in all the ways you
270             would expect. In addition, it also supports a limited form of type coercion
271             inheritance. Generally speaking, type constraints don't inherit coercions since
272             this would rapidly become confusing. However, since your parameterizable type
273             is intended to become parameterized in order to be useful, we support inheriting
274             from a 'base' parameterizable type constraint to its 'child' parameterized sub
275             types.
276              
277             For the purposes of this discussion, a parameterizable type is a subtype created
278             when you say, "as Parameterizable[..." in your sub type declaration. For
279             example:
280              
281             subtype Varchar,
282             as Parameterizable[Str, Int],
283             where {
284             my($string, $int) = @_;
285             $int >= length($string) ? 1:0;
286             },
287             message { "'$_[0]' is too long (max length $_[1])" };
288              
289             This is the L</SYNOPSIS> example, which creates a new parameterizable subtype of
290             Str which takes a single type parameter which must be an Int. This Int is used
291             to constrain the allowed length of the Str value.
292              
293             Now, this new sub type, "Varchar", is parameterizable since it can take a type
294             parameter. We can apply some coercions to it:
295              
296             coerce Varchar,
297             from Object,
298             via { "$_" }, ## stringify the object
299             from ArrayRef,
300             via { join '', @$_ }; ## convert array to string
301              
302             This parameterizable subtype, "Varchar" itself is something you'd never use
303             directly to constraint a value. In other words you'd never do something like:
304              
305             has name => (isa=>Varchar, ...); ## Why not just use a Str?
306              
307             You are going to do this:
308              
309             has name => (isa=>Varchar[40], ...)
310              
311             Which is actually useful. However, "Varchar[40]" is a parameterized type, it
312             is a subtype of the parameterizable "Varchar" and it inherits coercions from
313             its parent. This may be a bit surprising to L<Moose> developers, but I believe
314             this is the actual desired behavior.
315              
316             You can of course add new coercions to a subtype of a parameterizable type:
317              
318             subtype MySpecialVarchar,
319             as Varchar;
320              
321             coerce MySpecialVarchar,
322             from ...
323              
324             In which case this new parameterizable type would NOT inherit coercions from
325             it's parent parameterizable type (Varchar). This is done in keeping with how
326             generally speaking L<Moose> type constraints avoid complicated coercion inheritance
327             schemes, however I am open to discussion if there are valid use cases.
328              
329             NOTE: One thing you can't do is add a coercion to an already parameterized type.
330             Currently the following would throw a hard error:
331              
332             subtype 40CharStr,
333             as Varchar[40];
334              
335             coerce 40CharStr, ... # BANG!
336              
337             This limitation is enforced since generally we expect coercions on the parent.
338             However if good use cases arise we may lift this in the future.
339              
340             In general we are trying to take a conservative approach that keeps in line with
341             how most L<Moose> authors expect type constraints to work.
342              
343             =head2 Recursion
344              
345             TBD - Needs a use case... Anyone?
346              
347             =head1 TYPE CONSTRAINTS
348              
349             This type library defines the following constraints.
350              
351             =head2 Parameterizable[ParentTypeConstraint, ConstrainingValueTypeConstraint]
352              
353             Create a subtype of ParentTypeConstraint with a dependency on a value that can
354             pass the ConstrainingValueTypeConstraint. If ConstrainingValueTypeConstraint is empty
355             we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
356             This is useful if you are creating some base Parameterizable type constraints
357             that you intend to sub class.
358              
359             =head1 SEE ALSO
360              
361             The following modules or resources may be of interest.
362              
363             L<Moose>, L<Moose::Meta::TypeConstraint>, L<MooseX::Types>
364              
365             =head1 AUTHOR
366              
367             John Napiorkowski, C<< <jjnapiork@cpan.org> >>
368              
369             =head1 COPYRIGHT & LICENSE
370              
371             This program is free software; you can redistribute it and/or modify
372             it under the same terms as Perl itself.
373              
374             =cut
375