use warnings;
use Carp 'confess';
-use Sub::Name 'subname';
use Scalar::Util 'blessed';
-our $VERSION = '0.02';
+our $VERSION = '0.05';
use Moose::Meta::TypeConstraint;
+use Moose::Meta::TypeCoercion;
-sub import {
- shift;
- my $pkg = shift || caller();
- return if $pkg eq ':no_export';
- no strict 'refs';
- foreach my $export (qw(type subtype as where coerce from via)) {
- *{"${pkg}::${export}"} = \&{"${export}"};
- }
+{
+ require Sub::Exporter;
+
+ my @exports = qw[type subtype as where message coerce from via find_type_constraint];
+
+ Sub::Exporter->import(
+ -setup => {
+ exports => \@exports,
+ groups => {
+ default => [':all']
+ }
+ }
+ );
}
{
my %TYPES;
sub find_type_constraint {
- my $type_name = shift;
- $TYPES{$type_name};
+ return $TYPES{$_[0]}->[1]
+ if exists $TYPES{$_[0]};
+ return;
}
-
- sub register_type_constraint {
- my ($name, $parent, $constraint) = @_;
- (not exists $TYPES{$name})
- || confess "The type constraint '$name' has already been registered";
+
+ sub _dump_type_constraints {
+ require Data::Dumper;
+ Data::Dumper::Dumper(\%TYPES);
+ }
+
+ sub _create_type_constraint {
+ my ($name, $parent, $check, $message) = @_;
+ my $pkg_defined_in = scalar(caller(1));
+ ($TYPES{$name}->[0] eq $pkg_defined_in)
+ || confess "The type constraint '$name' has already been created "
+ if defined $name && exists $TYPES{$name};
$parent = find_type_constraint($parent) if defined $parent;
- $TYPES{$name} = Moose::Meta::TypeConstraint->new(
- name => $name,
+ my $constraint = Moose::Meta::TypeConstraint->new(
+ name => $name || '__ANON__',
parent => $parent,
- constraint => $constraint,
+ constraint => $check,
+ message => $message,
);
+ $TYPES{$name} = [ $pkg_defined_in, $constraint ] if defined $name;
+ return $constraint;
}
- sub find_type_coercion {
- my $type_name = shift;
- $TYPES{$type_name}->coercion_code;
- }
-
- sub register_type_coercion {
- my ($type_name, $type_coercion) = @_;
- my $type = $TYPES{$type_name};
+ sub _install_type_coercions {
+ my ($type_name, $coercion_map) = @_;
+ my $type = find_type_constraint($type_name);
(!$type->has_coercion)
|| confess "The type coercion for '$type_name' has already been registered";
- $type->set_coercion_code($type_coercion);
+ my $type_coercion = Moose::Meta::TypeCoercion->new(
+ type_coercion_map => $coercion_map,
+ type_constraint => $type
+ );
+ $type->coercion($type_coercion);
}
sub export_type_contstraints_as_functions {
my $pkg = caller();
no strict 'refs';
foreach my $constraint (keys %TYPES) {
- *{"${pkg}::${constraint}"} = $TYPES{$constraint}->constraint_code;
+ *{"${pkg}::${constraint}"} = find_type_constraint($constraint)->_compiled_type_constraint;
}
}
}
+# type constructors
sub type ($$) {
my ($name, $check) = @_;
- register_type_constraint($name, undef, $check);
+ _create_type_constraint($name, undef, $check);
}
-sub subtype ($$;$) {
- if (scalar @_ == 3) {
- my ($name, $parent, $check) = @_;
- register_type_constraint($name, $parent, $check);
- }
- else {
- my ($parent, $check) = @_;
- $parent = find_type_constraint($parent);
- return Moose::Meta::TypeConstraint->new(
- name => '__ANON__',
- parent => $parent,
- constraint => $check,
- );
- }
+sub subtype ($$;$$) {
+ unshift @_ => undef if scalar @_ <= 2;
+ _create_type_constraint(@_);
}
sub coerce ($@) {
my ($type_name, @coercion_map) = @_;
- my @coercions;
- while (@coercion_map) {
- my ($constraint_name, $action) = splice(@coercion_map, 0, 2);
- my $constraint = find_type_constraint($constraint_name)->constraint_code;
- (defined $constraint)
- || confess "Could not find the type constraint ($constraint_name)";
- push @coercions => [ $constraint, $action ];
- }
- register_type_coercion($type_name, sub {
- my $thing = shift;
- foreach my $coercion (@coercions) {
- my ($constraint, $converter) = @$coercion;
- if (defined $constraint->($thing)) {
- local $_ = $thing;
- return $converter->($thing);
- }
- }
- return $thing;
- });
+ _install_type_coercions($type_name, \@coercion_map);
}
-sub as ($) { $_[0] }
-sub from ($) { $_[0] }
-sub where (&) { $_[0] }
-sub via (&) { $_[0] }
+sub as ($) { $_[0] }
+sub from ($) { $_[0] }
+sub where (&) { $_[0] }
+sub via (&) { $_[0] }
+sub message (&) { $_[0] }
# define some basic types
-type Any => where { 1 };
+type 'Any' => where { 1 }; # meta-type including all
+type 'Item' => where { 1 }; # base-type
+
+subtype 'Undef' => as 'Item' => where { !defined($_) };
+subtype 'Defined' => as 'Item' => where { defined($_) };
+
+subtype 'Value' => as 'Item' => where { !ref($_) };
+subtype 'Ref' => as 'Item' => where { ref($_) };
-type Value => where { !ref($_) };
-type Ref => where { ref($_) };
+subtype 'Bool' => as 'Item' => where { "$_" eq '1' || "$_" eq '0' };
-subtype Int => as Value => where { Scalar::Util::looks_like_number($_) };
-subtype Str => as Value => where { !Scalar::Util::looks_like_number($_) };
+subtype 'Int' => as 'Value' => where { Scalar::Util::looks_like_number($_) };
+subtype 'Str' => as 'Value' => where { !Scalar::Util::looks_like_number($_) };
-subtype ScalarRef => as Ref => where { ref($_) eq 'SCALAR' };
-subtype ArrayRef => as Ref => where { ref($_) eq 'ARRAY' };
-subtype HashRef => as Ref => where { ref($_) eq 'HASH' };
-subtype CodeRef => as Ref => where { ref($_) eq 'CODE' };
-subtype RegexpRef => as Ref => where { ref($_) eq 'Regexp' };
+subtype 'ScalarRef' => as 'Ref' => where { ref($_) eq 'SCALAR' };
+
+subtype 'CollectionRef' => as 'Ref' => where { ref($_) eq 'ARRAY' || ref($_) eq 'HASH' };
+
+subtype 'ArrayRef' => as 'CollectionRef' => where { ref($_) eq 'ARRAY' };
+subtype 'HashRef' => as 'CollectionRef' => where { ref($_) eq 'HASH' };
+
+subtype 'CodeRef' => as 'Ref' => where { ref($_) eq 'CODE' };
+subtype 'RegexpRef' => as 'Ref' => where { ref($_) eq 'Regexp' };
# NOTE:
# blessed(qr/.../) returns true,.. how odd
-subtype Object => as Ref => where { blessed($_) && blessed($_) ne 'Regexp' };
+subtype 'Object' => as 'Ref' => where { blessed($_) && blessed($_) ne 'Regexp' };
+
+subtype 'Role' => as 'Object' => where { $_->can('does') };
1;
subtype NaturalLessThanTen
=> as Natural
- => where { $_ < 10 };
+ => where { $_ < 10 }
+ => message { "This number ($_) is not less than ten!" };
coerce Num
=> from Str
to be are used in both attribute definitions and for method argument
validation.
-This is B<NOT> a type system for Perl 5.
+=head2 Important Caveat
+
+This is B<NOT> a type system for Perl 5. These are type constraints,
+and they are not used by Moose unless you tell it to. No type
+inference is performed, expression are not typed, etc. etc. etc.
+
+This is simply a means of creating small constraint functions which
+can be used to simplify your own type-checking code.
+
+=head2 Default Type Constraints
This module also provides a simple hierarchy for Perl 5 types, this
could probably use some work, but it works for me at the moment.
Any
+
+ Item
+ Undef
+ Defined
+ Bool
Value
Int
Str
Ref
ScalarRef
- ArrayRef
- HashRef
+ CollectionRef
+ ArrayRef
+ HashRef
CodeRef
RegexpRef
Object
+ Role
-Suggestions for improvement are welcome.
+Suggestions for improvement are welcome.
=head1 FUNCTIONS
=item B<find_type_constraint ($type_name)>
-=item B<register_type_constraint ($type_name, $type_constraint)>
-
-=item B<find_type_coercion>
-
-=item B<register_type_coercion>
+This function can be used to locate a specific type constraint
+meta-object. What you do with it from there is up to you :)
=item B<export_type_contstraints_as_functions>
-=item B<dump_type_constraints>
+This will export all the current type constraints as functions
+into the caller's namespace. Right now, this is mostly used for
+testing, but it might prove useful to others.
=back
=head2 Type Constraint Constructors
+The following functions are used to create type constraints.
+They will then register the type constraints in a global store
+where Moose can get to them if it needs to.
+
+See the L<SYNOPOSIS> for an example of how to use these.
+
=over 4
-=item B<type>
+=item B<type ($name, $where_clause)>
-=item B<subtype>
+This creates a base type, which has no parent.
-=item B<as>
+=item B<subtype ($name, $parent, $where_clause, ?$message)>
-=item B<where>
+This creates a named subtype.
-=item B<coerce>
+=item B<subtype ($parent, $where_clause, ?$message)>
-=item B<from>
+This creates an unnamed subtype and will return the type
+constraint meta-object, which will be an instance of
+L<Moose::Meta::TypeConstraint>.
-=item B<via>
+=item B<as>
-=back
+This is just sugar for the type constraint construction syntax.
-=head2 Built-in Type Constraints
+=item B<where>
-=over 4
+This is just sugar for the type constraint construction syntax.
-=item B<Any>
+=item B<message>
-=item B<Value>
+This is just sugar for the type constraint construction syntax.
-=item B<Int>
+=back
-=item B<Str>
+=head2 Type Coercion Constructors
-=item B<Ref>
+Type constraints can also contain type coercions as well. In most
+cases Moose will run the type-coercion code first, followed by the
+type constraint check. This feature should be used carefully as it
+is very powerful and could easily take off a limb if you are not
+careful.
-=item B<ArrayRef>
+See the L<SYNOPOSIS> for an example of how to use these.
-=item B<CodeRef>
+=over 4
-=item B<HashRef>
+=item B<coerce>
-=item B<RegexpRef>
+=item B<from>
-=item B<ScalarRef>
+This is just sugar for the type coercion construction syntax.
+
+=item B<via>
-=item B<Object>
+This is just sugar for the type coercion construction syntax.
=back