Create your own parameterizable types for Moose and other uses
Perl
Pull request Compare This branch is 38 commits behind jjn1056:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
docs
lib/MooseX
t
Changes
MANIFEST.SKIP
Makefile.PL
README

README

NAME
    MooseX::Types::Parameterizable - Create your own Parameterizable Types.

SYNOPSIS
    The follow is example usage.

        package Test::MooseX::Types::Parameterizable::Synopsis;

        use Moose;
        use MooseX::Types::Parameterizable qw(Parameterizable);
        use MooseX::Types::Moose qw(Str Int ArrayRef);
        use MooseX::Types -declare=>[qw(Varchar)];

    Create a type constraint that is similar to SQL Varchar type.

        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 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, 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.

    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. 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});
            };

    Example subtype with additional constraints:

        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 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
    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 - Needs a use case... Anyone?

TYPE CONSTRAINTS
    This type library defines the following constraints.

  Parameterizable[ParentTypeConstraint, ConstrainingValueTypeConstraint]
    Create a subtype of ParentTypeConstraint with a dependency on a value
    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.

    Moose, Moose::Meta::TypeConstraint, MooseX::Types

AUTHOR
    John Napiorkowski, "<jjnapiork@cpan.org>"

COPYRIGHT & LICENSE
    This program is free software; you can redistribute it and/or modify it
    under the same terms as Perl itself.