MooseX::Types::Parameterizable - Create your own Parameterizable Types.
SYNOPSIS
- Within your MooseX::Types declared library module:
+ The follow is example usage.
+ package Test::MooseX::Types::Parameterizable::Synopsis;
+
+ use Moose;
use MooseX::Types::Parameterizable qw(Parameterizable);
-
- subtype Set,
- as class_type("Set::Scalar");
+ use MooseX::Types::Moose qw(Str Int ArrayRef);
+ use MooseX::Types -declare=>[qw(Varchar)];
- subtype UniqueInt,
- as Parameterizable[Int, Set],
- where {
- my ($int, $set) = @_;
- return !$set->has($int);
- };
-
- subtype PositiveSet,
- as Set,
- where {
- my ($set) = @_;
- return !grep {$_ <0 } $set->members;
- };
-
- subtype PositiveUniqueInt,
- as UniqueInt[PositiveSet];
-
- my $set = Set::Scalar->new(1,2,3);
+ Create a type constraint that is similar to SQL Varchar type.
- UniqueInt([$set])->check(100); ## Okay, 100 isn't in (1,2,3)
- UniqueInt([$set])->check(-99); ## Okay, -99 isn't in (1,2,3)
- UniqueInt([$set])->check(2); ## Not OK, 2 is in (1,2,3)
-
- PositiveUniqueInt([$set])->check(100); ## Okay, 100 isn't in (1,2,3)
- PositiveUniqueInt([$set])->check(-99); ## Not OK, -99 not Positive Int
- PositiveUniqueInt([$set])->check(2); ## Not OK, 2 is in (1,2,3)
-
- my $negative_set = Set::Scalar->new(-1,-2,-3);
-
- UniqueInt([$negative_set])->check(100); ## Throws exception
+ subtype Varchar,
+ as Parameterizable[Str,Int],
+ where {
+ my($string, $int) = @_;
+ $int >= length($string) ? 1:0;
+ },
+ message { "'$_' is too long" };
+
+ Coerce an ArrayRef to a string via concatenation.
+
+ coerce Varchar,
+ from ArrayRef,
+ via {
+ my ($arrayref, $int) = @_;
+ join('', @$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
+
+ my $object1 = __PACKAGE__->new(
+ varchar_five => '1234',
+ varchar_ten => '123456789',
+ );
+
+ 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
+
+ my $object3 = __PACKAGE__->new(
+ varchar_five => [qw/aa bb/], ## coerces to "aabb"
+ varchar_ten => '123456789',
+ );
+
+ See t/05-pod-examples.t for runnable versions of all POD code
DESCRIPTION
A MooseX::Types library for creating parameterizable types. A
parameterizable type constraint for all intents and uses is a subclass
- of a parent type, but adds a secondary type parameter which is available
- to constraint callbacks (such as inside the 'where' clause) or in the
- coercions.
-
- This allows you to create a type that has additional runtime advice,
- such as a set of numbers within which another number must be unique, or
- allowable ranges for a integer, such as in:
-
- subtype Range,
- as Dict[max=>Int, min=>Int],
- where {
- my ($range) = @_;
- return $range->{max} > $range->{min};
- };
-
- subtype RangedInt,
- as Parameterizable[Int, Range],
- where {
- my ($value, $range) = @_;
- return ($value >= $range->{min} &&
- $value <= $range->{max});
- };
-
- RangedInt([{min=>10,max=>100}])->check(50); ## OK
- RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max
-
- This throws a hard Moose exception. You'll need to capture it in an eval
- or related exception catching system (see TryCatch).
-
- RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
-
- If you can't accept a hard exception here, you'll need to test the
- constraining values first, as in:
-
- my $range = {min=>99, max=>10};
- if(my $err = Range->validate($range)) {
- ## Handle #$err
- } else {
- RangedInt($range)->check(99);
- }
+ of a parent type, but adds additional type parameters which are
+ available to constraint callbacks (such as inside the 'where' clause of
+ a type constraint definition) or in the coercions you define for a given
+ type constraint.
+
+ If you have Moose experience, you probably are familiar with the builtin
+ parameterizable type constraints 'ArrayRef' and 'HashRef'. This type
+ constraint lets you generate your own versions of parameterized
+ constraints that work similarly. See Moose::Util::TypeConstraints for
+ more.
+
+ Using this type constraint, you can generate new type constraints that
+ have additional runtime advice, such as being able to specify maximum
+ and minimum values for an Int (integer) type constraint:
+
+ subtype Range,
+ as Dict[max=>Int, min=>Int],
+ where {
+ my ($range) = @_;
+ return $range->{max} > $range->{min};
+ };
+
+ subtype RangedInt,
+ as Parameterizable[Int, Range],
+ where {
+ my ($value, $range) = @_;
+ return ($value >= $range->{min} &&
+ $value <= $range->{max});
+ };
+
+ RangedInt([{min=>10,max=>100}])->check(50); ## OK
+ RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, exceeds max
+
+ This is useful since it lets you generate common patterns of type
+ constraints rather than build a custom type constraint for all similar
+ cases.
+
+ The type parameter must be valid against the 'constrainting' type
+ constraint used in the Parameterizable condition. 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 TryCatch or
+ 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!
+
+ In the above case the 'min' value is larger than the 'max', which
+ violates the Range constraint. We throw a hard error here since I think
+ incorrect type parameters are most likely to be the result of a typo or
+ other true error conditions.
+
+ If you can't accept a hard exception here, you can either trap it as
+ advised above or you need to test the constraining values first, as in:
+
+ my $range = {min=>99, max=>10};
+ if(my $err = Range->validate($range)) {
+ ## Handle #$err
+ } else {
+ RangedInt($range)->check(99);
+ }
Please note that for ArrayRef or HashRef parameterizable type
constraints, as in the example above, as a convenience we automatically
ref the incoming type 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=>10,max=>100])->check(50); ## OK
+ 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 your type parameters don't match the required reference type.
+ conciseness of your type constraint declarations.
- Also not that if you 'chain' parameterization results with a method call
- like:
+ Also note that if you 'chain' parameterization results with a method
+ call like:
- TypeConstraint([$ob])->method;
+ TypeConstraint([$ob])->method;
You need to have the "(...)" around the ArrayRef in the Type Constraint
- parameters. This seems to have something to do with the precendent level
- of "->". Patches or thoughts welcomed. You only need to do this in the
- above case which I imagine is not a very common case.
-
- ==head2 Subtyping a Parameterizable type constraints
+ parameters. You can skip the wrapping parenthesis in the most common
+ cases, such as when you use the type constraint in the options section
+ of a Moose attribute declaration, or when defining type libraries.
+ 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 type constraints are a subtype of the parent.
For example:
- subtype RangedInt,
- as Parameterizable[Int, Range],
- where {
- my ($value, $range) = @_;
- return ($value >= $range->{min} &&
- $value =< $range->{max});
- };
+ subtype RangedInt,
+ as Parameterizable[Int, Range],
+ where {
+ my ($value, $range) = @_;
+ return ($value >= $range->{min} &&
+ $value =< $range->{max});
+ };
Example subtype with additional constraints:
- subtype PositiveRangedInt,
- as RangedInt,
- where {
- shift >= 0;
- };
-
- Or you could have done the following instead:
-
- ## Subtype of Int for positive numbers
- subtype PositiveInt,
- as Int,
- where {
- my ($value, $range) = @_;
- return $value >= 0;
- };
-
- ## subtype Range to re-parameterize Range with subtypes
- subtype PositiveRange,
- as Range[max=>PositiveInt, min=>PositiveInt];
-
- ## create subtype via reparameterizing
- subtype PositiveRangedInt,
- as RangedInt[PositiveRange];
+ subtype PositiveRangedInt,
+ as RangedInt,
+ where {
+ shift >= 0;
+ };
+
+ In this case you'd now have a parameterizable type constraint which
+ would work like:
+
+ Test::More::ok PositiveRangedInt([{min=>-10, max=>75}])->check(5);
+ Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5);
+
+ Of course the above is somewhat counter-intuitive to the reader, since
+ we have defined our 'RangedInt' in such as way as to let you declare
+ negative ranges. For the moment each type constraint rule is apply
+ without knowledge of any other rule, nor can a rule 'inform' existing
+ rules. This is a limitation of the current system. However, you could
+ instead do the following:
+
+ ## Subtype of Int for positive numbers
+ subtype PositiveInt,
+ as Int,
+ where {
+ my ($value, $range) = @_;
+ return $value >= 0;
+ };
+
+ ## subtype Range to re-parameterize Range with subtypes
+ subtype PositiveRange,
+ as Range[max=>PositiveInt, min=>PositiveInt];
+
+ ## create subtype via reparameterizing
+ subtype PositiveRangedInt,
+ as RangedInt[PositiveRange];
+
+ This would constrain values in the same way as the previous type
+ constraint but have the bonus that you'd throw a hard exception if you
+ try to use an incorrect range:
+
+ Test::More::ok PositiveRangedInt([{min=>10, max=>75}])->check(15); ## OK
+ Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5); ## Dies
Notice how re-parameterizing the parameterizable type 'RangedInt' works
slightly differently from re-parameterizing 'PositiveRange' Although it
initially takes two type constraint values to declare a parameterizable
type, should you wish to later re-parameterize it, you only use a
- subtype of the second type parameter (the parameterizable type
- constraint) since the first type constraint sets the parent type for the
- parameterizable type. In other words, given the example above, a type
- constraint of 'RangedInt' would have a parent of 'Int', not
- 'Parameterizable' and for all intends and uses you could stick it
- wherever you'd need an Int.
-
- subtype NameAge,
- as Tuple[Str, Int];
-
- ## re-parameterized subtypes of NameAge containing a Parameterizable Int
- subtype NameBetween18and35Age,
- as NameAge[
- Str,
- PositiveRangedInt[min=>18,max=>35],
- ];
-
- One caveat is that you can't stick an unparameterized parameterizable
- type inside a structure, such as MooseX::Types::Structured since that
- would require the ability to convert a 'containing' type constraint into
- a parameterizable type, which is a capacity we current don't have.
+ subtype of the extra type parameter (the parameterizable type
+ constraints) since the first type constraint sets the parent type for
+ the parameterizable type.
+
+ In other words, given the example above, a type constraint of
+ 'RangedInt' would have a parent of 'Int', not 'Parameterizable' and for
+ all intends and uses you could stick it wherever you'd need an Int. You
+ can't change the parent, even to make it a subclass of Int.
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
- parameterizable type. So for example if you create a Parameterizable
- type like:
-
- subtype RequiredAgeInYears,
- as Int;
-
- subtype PersonOverAge,
- as Parameterizable[Person, RequiredAgeInYears]
- where {
- my ($person, $required_years_old) = @_;
- return $person->years_old > $required_years_old;
- }
-
- 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);
- };
+ A type coercion is a rule that allows you to transform one type from one
+ or more other types. Please see Moose::Cookbook::Basics::Recipe5 for an
+ example of type coercions if you are not familiar with the subject.
+
+ MooseX::Types::Parameterizable supports 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.
+
+ 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 Varchar,
+ as Parameterizable[Str, Int],
+ where {
+ my($string, $int) = @_;
+ $int >= length($string) ? 1:0;
+ },
+ message { "'$_' is too long" };
+
+ 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, ...); ## Why not just use a Str?
+
+ 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 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 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 Moose authors expect type constraints to work.
Recursion
- TBD
+ TBD - Needs a use case... Anyone?
TYPE CONSTRAINTS
This type library defines the following constraints.
- Parameterizable[ParentTypeConstraint, ParameterizableValueTypeConstraint]
+ Parameterizable[ParentTypeConstraint, ConstrainingValueTypeConstraint]
Create a subtype of ParentTypeConstraint with a dependency on a value
- that can pass the ParameterizableValueTypeConstraint. If
- ParameterizableValueTypeConstraint is empty we default to the 'Any' type
- constraint (see Moose::Util::TypeConstraints).
+ that can pass the ConstrainingValueTypeConstraint. If
+ ConstrainingValueTypeConstraint is empty we default to the 'Any' type
+ constraint (see Moose::Util::TypeConstraints). This is useful if you are
+ creating some base Parameterizable type constraints that you intend to
+ sub class.
+
+SEE ALSO
+ The following modules or resources may be of interest.
- This creates a type constraint which must be further parameterized at
- later time before it can be used to ->check or ->validate a value.
- Attempting to do so will cause an exception.
+ Moose, Moose::Meta::TypeConstraint, MooseX::Types
AUTHOR
John Napiorkowski, "<jjnapiork@cpan.org>"