use 5.008;
-our $VERSION = '0.02';
+our $VERSION = '0.03';
$VERSION = eval $VERSION;
use Moose::Util::TypeConstraints;
use MooseX::Types::Moose qw(Str Int ArrayRef);
use MooseX::Types -declare=>[qw(Varchar)];
- ## Create a type constraint that is a string but parameterizes an integer
- ## that is used as a maximum length constraint on that string, similar to
- ## a SQL Varchar database type.
+Create a type constraint that is similar to SQL Varchar type.
subtype Varchar,
as Parameterizable[Str,Int],
},
message { "'$_' is too long" };
- ## Coerce an ArrayRef to a string via concatenation.
+Coerce an ArrayRef to a string via concatenation.
coerce Varchar,
from ArrayRef,
has 'varchar_five' => (isa=>Varchar[5], is=>'ro', coerce=>1);
has 'varchar_ten' => (isa=>Varchar[10], is=>'ro');
- ## Object created since attributes are valid
+Object created since attributes are valid
+
my $object1 = __PACKAGE__->new(
varchar_five => '1234',
varchar_ten => '123456789',
);
- ## Dies with an invalid constraint for 'varchar_five'
+Dies with an invalid constraint for 'varchar_five'
+
my $object2 = __PACKAGE__->new(
varchar_five => '12345678', ## too long!
varchar_ten => '123456789',
);
- ## varchar_five coerces as expected
+varchar_five coerces as expected
+
my $object3 = __PACKAGE__->new(
varchar_five => [qw/aa bb/], ## coerces to "aabb"
varchar_ten => '123456789',
};
RangedInt([{min=>10,max=>100}])->check(50); ## OK
- RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max
+ RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, exceeds max
The type parameter must be valid against the type constraint given. If you pass
an invalid value this throws a hard Moose exception. You'll need to capture it
-in an eval or related exception catching system (see L<TryCatch> or <Try::Tiny>.)
+in an eval or related exception catching system (see L<TryCatch> or L<Try::Tiny>.)
For example the following would throw a hard error (and not just return false)
RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
parameters, so that the above could also be written as:
RangedInt([min=>10,max=>100])->check(50); ## OK
- RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max
- RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range!
+ RangedInt([min=>50, max=>75])->check(99); ## Not OK, exceeds max
+ RangedInt([min=>99, max=>10])->check(10); ## Exception, not valid Range
This is the preferred syntax, as it improve readability and adds to the
conciseness of your type constraint declarations. An exception wil be thrown if
such as when you use the type constraint in the options section of a L<Moose>
attribute declaration, or when defining type libraries.
-==head2 Subtyping a Parameterizable type constraints
+=head2 Subtyping a Parameterizable type constraints
When subclassing a parameterizable type you must be careful to match either the
required type parameter type constraint, or if re-parameterizing, the new
=head2 Coercions
-Parameterizable types have some limited support for coercions. Several things must
-be kept in mind. The first is that the coercion targets the type constraint
-which is being made parameterizable, Not the parameterized type. So for example if you
-create a Parameterizable type like:
+A type coerction is a rule that allows you to transform one type from one or
+more other types. Please see L<Moose::Cookbook::Basics::Recipe5> for an example
+of type coercions if you are not familiar with the subject.
+
+L<MooseX::Types::Parameterizable> support type coercions in all the ways you
+would expect. In addition, it also supports a limited form of type coercion
+inheritance. Generally speaking, type constraints don't inherit coercions since
+this would rapidly become confusing. However, since your parameterizable type
+is intended to become parameterized in order to be useful, we support inheriting
+from a 'base' parameterizable type constraint to its 'child' parameterized sub
+types.
- subtype RequiredAgeInYears,
- as Int;
+For the purposes of this discussion, a parameterizable type is a subtype created
+when you say, "as Parameterizable[..." in your sub type declaration. For example
- subtype PersonOverAge,
- as Parameterizable[Person, RequiredAgeInYears]
+ subtype Varchar,
+ as Parameterizable[Str, Int],
where {
- my ($person, $required_years_old) = @_;
- return $person->years_old > $required_years_old;
- }
+ my($string, $int) = @_;
+ $int >= length($string) ? 1:0;
+ },
+ message { "'$_' is too long" };
-This would validate the following:
-
- my $person = Person->new(age=>35);
- PersonOverAge([18])->check($person);
-
-You can then apply the following coercion
-
- coerce PersonOverAge,
- from Dict[age=>int],
- via {Person->new(%$_)},
- from Int,
- via {Person->new(age=>$_)};
-
-This coercion would then apply to all the following:
-
- PersonOverAge([18])->check(30); ## via the Int coercion
- PersonOverAge([18])->check({age=>50}); ## via the Dict coercion
-
-However, you are not allowed to place coercions on parameterizable types that have
-had their constraining value filled, nor subtypes of such. For example:
-
- coerce PersonOverAge[18],
- from DateTime,
- via {$_->years};
-
-That would generate a hard exception. This is a limitation for now until I can
-devise a smarter way to cache the generated type constraints. However, I doubt
-it will be a significant limitation, since the general use case is supported.
-
-Lastly, the constraining value is available in the coercion in much the same way
-it is available to the constraint.
-
- ## Create a type constraint where a Person must be in the set
- subtype PersonInSet,
- as Parameterizable[Person, PersonSet],
- where {
- my ($person, $person_set) = @_;
- $person_set->find($person);
- }
-
- coerce PersonInSet,
- from HashRef,
- via {
- my ($hashref, $person_set) = @_;
- return $person_set->create($hash_ref);
- };
+This is the </SYNOPSIS> example, which creates a new parameterizable subtype of
+Str which takes a single type parameter which must be an Int. This Int is used
+to constrain the allowed length of the Str value.
+
+Now, this new sub type, "Varchar", is parameterizable since it can take a type
+parameter. We can apply some coercions to it:
+
+ coerce Varchar,
+ from Object,
+ via { "$_"; }, ## stringify the object
+ from ArrayRef,
+ via { join '',@$_ }; ## convert array to string
+
+This parameterizable subtype, "Varchar" itself is something you'd never use
+directly to constraint a value. In other words you'd never do something like:
+
+ has name => (isa=>Varchar, ...)
+
+You are going to do this:
+
+ has name => (isa=>Varchar[40], ...)
+
+Which is actually useful. However, "Varchar[40]" is a parameterized type, it
+is a subtype of the parameterizable "Varchar" and it inherits coercions from
+its parent. This may be a bit surprising to L<Moose> developers, but I believe
+this is the actual desired behavior.
+
+You can of course add new coercions to a subtype of a parameterizable type:
+
+ subtype MySpecialVarchar,
+ as Varchar;
+
+ coerce MySpecialVarchar,
+ from ...
+
+In which case this new parameterizable type would NOT inherit coercions from
+it's parent parameterizable type (Varchar). This is done in keeping with how
+generally speaking L<Moose> type constraints avoid complicated coercion inheritance
+schemes, however I am open to discussion if there are valid use cases.
+
+NOTE: One thing you can't do is add a coercion to an already parameterized type.
+Currently the following would throw a hard error:
+
+ subtype 40CharStr,
+ as Varchar[40];
+
+ coerce 40CharStr, ... # BANG!
+
+This limitation is enforced since generally we expect coercions on the parent.
+However if good use cases arise we may lift this in the future.
+
+In general we are trying to take a conservative approach that keeps in line with
+how most L<Moose> authors expect type constraints to work.
=head2 Recursion
- TBD - Need more tests.
+ TBD - Needs a use case... Anyone?
=head1 TYPE CONSTRAINTS