package Moose::Util::TypeConstraints;
-use strict;
-use warnings;
-
use Carp ();
use List::MoreUtils qw( all any );
use Scalar::Util qw( blessed reftype );
use Moose::Exporter;
-our $VERSION = '0.75_01';
+our $VERSION = '0.93_03';
$VERSION = eval $VERSION;
our $AUTHORITY = 'cpan:STEVAN';
use Moose::Meta::TypeConstraint::Class;
use Moose::Meta::TypeConstraint::Role;
use Moose::Meta::TypeConstraint::Enum;
+use Moose::Meta::TypeConstraint::DuckType;
use Moose::Meta::TypeCoercion;
use Moose::Meta::TypeCoercion::Union;
use Moose::Meta::TypeConstraint::Registry;
coerce from via
enum
find_type_constraint
- register_type_constraint )
+ register_type_constraint
+ match_on_type )
],
- _export_to_main => 1,
);
## --------------------------------------------------------
@methods = @$type_name;
$type_name = undef;
}
+ if ( @methods == 1 && ref $methods[0] eq 'ARRAY' ) {
+ @methods = @{ $methods[0] };
+ }
register_type_constraint(
- _create_type_constraint(
+ create_duck_type_constraint(
$type_name,
- 'Object',
- sub {
- my $obj = $_;
- return 0 unless all { $obj->can($_) } @methods;
- return 1;
- },
- sub {
- my $obj = $_;
- my @missing_methods = grep { !$obj->can($_) } @methods;
- return
- "${\blessed($obj)} is missing methods '@missing_methods'";
- },
+ \@methods,
)
);
}
@values = @$type_name;
$type_name = undef;
}
+ if ( @values == 1 && ref $values[0] eq 'ARRAY' ) {
+ @values = @{ $values[0] };
+ }
( scalar @values >= 2 )
|| __PACKAGE__->_throw_error(
"You must have at least two values to enumerate through");
);
}
+sub create_duck_type_constraint {
+ my ( $type_name, $methods ) = @_;
+
+ Moose::Meta::TypeConstraint::DuckType->new(
+ name => $type_name || '__ANON__',
+ methods => $methods,
+ );
+}
+
+sub match_on_type {
+ my ($to_match, @cases) = @_;
+ my $default;
+ if (@cases % 2 != 0) {
+ $default = pop @cases;
+ (ref $default eq 'CODE')
+ || __PACKAGE__->_throw_error("Default case must be a CODE ref, not $default");
+ }
+ while (@cases) {
+ my ($type, $action) = splice @cases, 0, 2;
+
+ unless (blessed $type && $type->isa('Moose::Meta::TypeConstraint')) {
+ $type = find_or_parse_type_constraint($type)
+ || __PACKAGE__->_throw_error("Cannot find or parse the type '$type'")
+ }
+
+ (ref $action eq 'CODE')
+ || __PACKAGE__->_throw_error("Match action must be a CODE ref, not $action");
+
+ if ($type->check($to_match)) {
+ local $_ = $to_match;
+ return $action->($to_match);
+ }
+ }
+ (defined $default)
+ || __PACKAGE__->_throw_error("No cases matched for $to_match");
+ {
+ local $_ = $to_match;
+ return $default->($to_match);
+ }
+}
+
+
## --------------------------------------------------------
## desugaring functions ...
## --------------------------------------------------------
my $type = find_type_constraint($type_name);
( defined $type )
|| __PACKAGE__->_throw_error(
- "Cannot find type '$type_name', perhaps you forgot to load it.");
+ "Cannot find type '$type_name', perhaps you forgot to load it");
if ( $type->has_coercion ) {
$type->coercion->add_type_coercions(@$coercion_map);
}
use re "eval";
my $valid_chars = qr{[\w:\.]};
- my $type_atom = qr{ $valid_chars+ };
+ my $type_atom = qr{ (?>$valid_chars+) }x;
+ my $ws = qr{ (?>\s*) }x;
my $any;
- my $type = qr{ $valid_chars+ (?: \[ \s* (??{$any}) \s* \] )? }x;
+ my $type = qr{ $type_atom (?: \[ $ws (??{$any}) $ws \] )? }x;
my $type_capture_parts
- = qr{ ($valid_chars+) (?: \[ \s* ((??{$any})) \s* \] )? }x;
+ = qr{ ($type_atom) (?: \[ $ws ((??{$any})) $ws \] )? }x;
my $type_with_parameter
- = qr{ $valid_chars+ \[ \s* (??{$any}) \s* \] }x;
+ = qr{ $type_atom \[ $ws (??{$any}) $ws \] }x;
- my $op_union = qr{ \s* \| \s* }x;
- my $union = qr{ $type (?: $op_union $type )+ }x;
+ my $op_union = qr{ $ws \| $ws }x;
+ my $union = qr{ $type (?> (?: $op_union $type )+ ) }x;
$any = qr{ $type | $union }x;
Moose::Meta::TypeConstraint::Class
Moose::Meta::TypeConstraint::Role
Moose::Meta::TypeConstraint::Enum
+ Moose::Meta::TypeConstraint::DuckType
Moose::Meta::TypeConstraint::Registry
);
subtype 'Ref' => as 'Defined' => where { ref($_) } =>
optimize_as \&Moose::Util::TypeConstraints::OptimizedConstraints::Ref;
-subtype 'Str' => as 'Value' => where {1} =>
+subtype 'Str' => as 'Value' => where { ref(\$_) eq 'SCALAR' } =>
optimize_as \&Moose::Util::TypeConstraints::OptimizedConstraints::Str;
subtype 'Num' => as 'Str' =>
where { blessed($_) && blessed($_) ne 'Regexp' } =>
optimize_as \&Moose::Util::TypeConstraints::OptimizedConstraints::Object;
+# This type is deprecated.
subtype 'Role' => as 'Object' => where { $_->can('does') } =>
optimize_as \&Moose::Util::TypeConstraints::OptimizedConstraints::Role;
Undef
Defined
Value
- Num
- Int
Str
- ClassName
- RoleName
+ Num
+ Int
+ ClassName
+ RoleName
Ref
ScalarRef
ArrayRef[`a]
CodeRef
RegexpRef
GlobRef
- FileHandle
+ FileHandle
Object
- Role
B<NOTE:> Any type followed by a type parameter C<[`a]> can be
parameterized, this means you can say:
type constraint to pass.
B<NOTE:> The C<RoleName> constraint checks a string is a I<package
-name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
-constraint checks that an I<object does> the named role.
+name> which is a role, like C<'MyApp::Role::Comparable'>.
=head2 Type Constraint Naming
=over 4
-=item B<subtype 'Name' => as 'Parent' => where { } ...>
+=item B<< subtype 'Name' => as 'Parent' => where { } ... >>
This creates a named subtype.
The valid hashref keys are C<as> (the parent), C<where>, C<message>,
and C<optimize_as>.
-=item B<subtype as 'Parent' => where { } ...>
+=item B<< subtype as 'Parent' => where { } ... >>
This creates an unnamed subtype and will return the type
constraint meta-object, which will be an instance of
Creates a type constraint for either C<undef> or something of the
given type.
-=item B<duck_type ($name, @methods)>
+=item B<duck_type ($name, \@methods)>
This will create a subtype of Object and test to make sure the value
-C<can()> do the methods in C<@methods>.
+C<can()> do the methods in C<\@methods>.
This is intended as an easy way to accept non-Moose objects that
provide a certain interface. If you're using Moose classes, we
=item B<duck_type (\@methods)>
-If passed an ARRAY reference instead of the C<$name>, C<@methods>
-pair, this will create an unnamed duck type. This can be used in an
-attribute definition like so:
+If passed an ARRAY reference as the only parameter instead of the
+C<$name>, C<\@methods> pair, this will create an unnamed duck type.
+This can be used in an attribute definition like so:
has 'cache' => (
is => 'ro',
isa => duck_type( [qw( get_set )] ),
);
-=item B<enum ($name, @values)>
+=item B<enum ($name, \@values)>
This will create a basic subtype for a given set of strings.
The resulting constraint will be a subtype of C<Str> and
-will match any of the items in C<@values>. It is case sensitive.
+will match any of the items in C<\@values>. It is case sensitive.
See the L<SYNOPSIS> for a simple example.
B<NOTE:> This is not a true proper enum type, it is simply
=item B<enum (\@values)>
-If passed an ARRAY reference instead of the C<$name>, C<@values> pair,
-this will create an unnamed enum. This can then be used in an attribute
-definition like so:
+If passed an ARRAY reference as the only parameter instead of the
+C<$name>, C<\@values> pair, this will create an unnamed enum. This
+can then be used in an attribute definition like so:
has 'sort_order' => (
is => 'ro',
all the built in types use this, so your subtypes (assuming they
are shallow) will not likely need to use this.
-=item B<type 'Name' => where { } ... >
+=item B<< type 'Name' => where { } ... >>
This creates a base type, which has no parent.
=back
+=head2 Type Constraint Utilities
+
+=over 4
+
+=item B<< match_on_type $value => ( $type => \&action, ... ?\&default ) >>
+
+This is a utility function for doing simple type based dispatching similar to
+match/case in O'Caml and case/of in Haskell. It is not as featureful as those
+languages, nor does not it support any kind of automatic destructuring
+bind. Here is a simple Perl pretty printer dispatching over the core Moose
+types.
+
+ sub ppprint {
+ my $x = shift;
+ match_on_type $x => (
+ HashRef => sub {
+ my $hash = shift;
+ '{ '
+ . (
+ join ", " => map { $_ . ' => ' . ppprint( $hash->{$_} ) }
+ sort keys %$hash
+ ) . ' }';
+ },
+ ArrayRef => sub {
+ my $array = shift;
+ '[ ' . ( join ", " => map { ppprint($_) } @$array ) . ' ]';
+ },
+ CodeRef => sub {'sub { ... }'},
+ RegexpRef => sub { 'qr/' . $_ . '/' },
+ GlobRef => sub { '*' . B::svref_2object($_)->NAME },
+ Object => sub { $_->can('to_string') ? $_->to_string : $_ },
+ ScalarRef => sub { '\\' . ppprint( ${$_} ) },
+ Num => sub {$_},
+ Str => sub { '"' . $_ . '"' },
+ Undef => sub {'undef'},
+ => sub { die "I don't know what $_ is" }
+ );
+ }
+
+Or a simple JSON serializer:
+
+ sub to_json {
+ my $x = shift;
+ match_on_type $x => (
+ HashRef => sub {
+ my $hash = shift;
+ '{ '
+ . (
+ join ", " =>
+ map { '"' . $_ . '" : ' . to_json( $hash->{$_} ) }
+ sort keys %$hash
+ ) . ' }';
+ },
+ ArrayRef => sub {
+ my $array = shift;
+ '[ ' . ( join ", " => map { to_json($_) } @$array ) . ' ]';
+ },
+ Num => sub {$_},
+ Str => sub { '"' . $_ . '"' },
+ Undef => sub {'null'},
+ => sub { die "$_ is not acceptable json type" }
+ );
+ }
+
+The matcher is done by mapping a C<$type> to an C<\&action>. The C<$type> can
+be either a string type or a L<Moose::Meta::TypeConstraint> object, and
+C<\&action> is a subroutine reference. This function will dispatch on the
+first match for C<$value>. It is possible to have a catch-all by providing an
+additional subroutine reference as the final argument to C<match_on_type>.
+
+=back
+
=head2 Type Coercion Constructors
You can define coercions for type constraints, which allow you to
Given a enum name this function will create a new
L<Moose::Meta::TypeConstraint::Enum> object for that enum name.
+=item B<create_duck_type_constraint($name, $methods)>
+
+Given a duck type name this function will create a new
+L<Moose::Meta::TypeConstraint::DuckType> object for that enum name.
+
=item B<find_or_parse_type_constraint($type_name)>
Given a type name, this first attempts to find a matching constraint
=head1 BUGS
-All complex software has bugs lurking in it, and this module is no
-exception. If you find a bug please either email me, or add the bug
-to cpan-RT.
+See L<Moose/BUGS> for details on reporting bugs.
=head1 AUTHOR
=head1 COPYRIGHT AND LICENSE
-Copyright 2006-2009 by Infinity Interactive, Inc.
+Copyright 2006-2010 by Infinity Interactive, Inc.
L<http://www.iinteractive.com>