use 5.008;
-our $VERSION = '0.01';
+our $VERSION = '0.02';
$VERSION = eval $VERSION;
use Moose::Util::TypeConstraints;
=head1 SYNOPSIS
-Within your L<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 a string but parameterizes an integer
+ ## that is used as a maximum length constraint on that string, similar to
+ ## a SQL Varchar database 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
+
=head1 DESCRIPTION
-A L<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.
+A L<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 additional type parameters which are available to constraint callbacks
+(such as inside the 'where' clause of a type constraint definition) or in the
+coercions.
+
+If you have L<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 L<Moose::Util::TypeConstraints> for more.
-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:
+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],
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 L<TryCatch> or <Try::Tiny>.)
+
+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>.)
+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!
conciseness of your type constraint declarations. An exception wil be thrown if
your type parameters don't match the required reference type.
-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;
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.
+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 L<Moose>
+attribute declaration, or when defining type libraries.
==head2 Subtyping a Parameterizable type constraints
shift >= 0;
};
-Or you could have done the following instead:
+In this case you'd now have a parameterizable type constraint called 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,
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 L<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.
+later re-parameterize it, you only use a 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.
=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 parameterizable 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 extra coercions to a parameterized type:
+
+ subtype Max40CharStr,
+ as Varchar[40];
+
+ coerce Max40CharStr,
+ from ...
+
+In which case this new parameterized type would inherit coercions from it's parent
+parameterizable type (Varchar), as well as the additional coercions you've added.
=head2 Recursion
- TBD
+ TBD - Need more tests.
=head1 TYPE CONSTRAINTS