Fix a var name that's always confused me.
[gitmo/MooseX-Types.git] / lib / MooseX / Types.pm
index 364faa2..e5047fd 100644 (file)
@@ -1,26 +1,21 @@
 package MooseX::Types;
 use Moose;
 
-=head1 NAME
-
-MooseX::Types - Organise your Moose types in libraries
-
-=cut
-
-#use warnings;
-#use strict;
+# ABSTRACT: Organise your Moose types in libraries
 
 use Moose::Util::TypeConstraints;
 use MooseX::Types::TypeDecorator;
-use MooseX::Types::Base             ();
-use MooseX::Types::Util             qw( filter_tags );
+use MooseX::Types::Base               ();
+use MooseX::Types::Util               qw( filter_tags );
 use MooseX::Types::UndefinedType;
-use Carp::Clan                      qw( ^MooseX::Types );
+use MooseX::Types::CheckedUtilExports ();
+use Carp::Clan                        qw( ^MooseX::Types );
+use Sub::Name;
+use Scalar::Util                      'reftype';
 
 use namespace::clean -except => [qw( meta )];
 
-our $VERSION = 0.06;
-
+use 5.008;
 my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'};
 
 =head1 SYNOPSIS
@@ -30,22 +25,27 @@ my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'};
   package MyLibrary;
 
   # predeclare our own types
-  use MooseX::Types 
-    -declare => [qw(
-        PositiveInt NegativeInt
-        ArrayRefOfPositiveInt ArrayRefOfAtLeastThreeNegativeInts
-        LotsOfInnerConstraints StrOrArrayRef
-    )];
+  use MooseX::Types -declare => [
+      qw(
+          PositiveInt
+          NegativeInt
+          ArrayRefOfPositiveInt
+          ArrayRefOfAtLeastThreeNegativeInts
+          LotsOfInnerConstraints
+          StrOrArrayRef
+          MyDateTime
+          )
+  ];
 
   # import builtin types
-  use MooseX::Types::Moose 'Int';
+  use MooseX::Types::Moose qw/Int HashRef/;
 
-  # type definition
-  subtype PositiveInt, 
-      as Int, 
+  # type definition.
+  subtype PositiveInt,
+      as Int,
       where { $_ > 0 },
       message { "Int is not larger than 0" };
-  
+
   subtype NegativeInt,
       as Int,
       where { $_ < 0 },
@@ -56,23 +56,35 @@ my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'};
       from Int,
           via { 1 };
 
-  # with parameterized constraints.  Please note the containing '(...)'
-  
+  # with parameterized constraints.
+
   subtype ArrayRefOfPositiveInt,
-    as (ArrayRef[PositiveInt]);
-    
+    as ArrayRef[PositiveInt];
+
   subtype ArrayRefOfAtLeastThreeNegativeInts,
-    as (ArrayRef[NegativeInt]),
+    as ArrayRef[NegativeInt],
     where { scalar(@$_) > 2 };
 
   subtype LotsOfInnerConstraints,
-    as (ArrayRef[ArrayRef[HashRef[Int]]]);
-    
+    as ArrayRef[ArrayRef[HashRef[Int]]];
+
   # with TypeConstraint Unions
-  
+
   subtype StrOrArrayRef,
     as Str|ArrayRef;
 
+  # class types
+
+  class_type 'DateTime';
+
+  # or better
+
+  class_type MyDateTime, { class => 'DateTime' };
+
+  coerce MyDateTime,
+    from HashRef,
+    via { DateTime->new(%$_) };
+
   1;
 
 =head2 Usage
@@ -105,37 +117,30 @@ my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'};
 
 =head1 DESCRIPTION
 
-The types provided with L<Moose> are by design global. This package helps
-you to organise and selectively import your own and the built-in types in
-libraries. As a nice side effect, it catches typos at compile-time too.
+The type system provided by Moose effectively makes all of its builtin type
+global, as are any types you declare with Moose. This means that every module
+that declares a type named "PositiveInt" is sharing the same type object. This
+can be a problem when different parts of the code base want to use the same
+name for different things.
 
-However, the main reason for this module is to provide an easy way to not
-have conflicts with your type names, since the internal fully qualified
-names of the types will be prefixed with the library's name.
+This package lets you declare types using short names, but behind the scenes
+it namespaces all your type declarations, effectively prevent name clashes
+between packages.
 
-This module will also provide you with some helper functions to make it 
-easier to use Moose types in your code.
+This is done by creating a type library module like C<MyApp::Types> and then
+importing types from that module into other modules.
 
-=head1 TYPE HANDLER FUNCTIONS
+As a side effect, the declaration mechanism allows you to write type names as
+barewords (really function calls), which catches typos in names at compile
+time rather than run time.
 
-=head2 $type
+This module also provides some helper functions for using Moose types outside
+of attribute declarations.
 
-A constant with the name of your type. It contains the type's fully
-qualified name. Takes no value, as all constants.
-
-=head2 is_$type
-
-This handler takes a value and tests if it is a valid value for this
-C<$type>. It will return true or false.
-
-=head2 to_$type
-
-A handler that will take a value and coerce it into the C<$type>. It will
-return a false value if the type could not be coerced.
-
-B<Important Note>: This handler will only be exported for types that can
-do type coercion. This has the advantage that a coercion to a type that
-cannot hasn't defined any coercions will lead to a compile-time error.
+If you mix string-based names with types created by this module, it will warn,
+with a few exceptions. If you are declaring a C<class_type()> or
+c<role_type()> within your type library, or if you use a fully qualified name
+like C<"MyApp::Foo">.
 
 =head1 LIBRARY DEFINITION
 
@@ -189,6 +194,27 @@ around L<Sub::Exporter>. This means you can do something like this:
   use MyLibrary TypeA => { -as => 'MyTypeA' },
                 TypeB => { -as => 'MyTypeB' };
 
+=head1 TYPE HANDLER FUNCTIONS
+
+=head2 $type
+
+A constant with the name of your type. It contains the type's fully
+qualified name. Takes no value, as all constants.
+
+=head2 is_$type
+
+This handler takes a value and tests if it is a valid value for this
+C<$type>. It will return true or false.
+
+=head2 to_$type
+
+A handler that will take a value and coerce it into the C<$type>. It will
+return a false value if the type could not be coerced.
+
+B<Important Note>: This handler will only be exported for types that can
+do type coercion. This has the advantage that a coercion to a type that
+has not defined any coercions will lead to a compile-time error.
+
 =head1 WRAPPING A LIBRARY
 
 You can define your own wrapper subclasses to manipulate the behaviour
@@ -196,7 +222,7 @@ of a set of library exports. Here is an example:
 
   package MyWrapper;
   use strict;
-  use Class::C3;
+  use MRO::Compat;
   use base 'MooseX::Types::Wrapper';
 
   sub coercion_export_generator {
@@ -224,8 +250,7 @@ with this:
   ...
   1;
 
-The C<Moose> library name is a special shortcut for 
-L<MooseX::Types::Moose>.
+The C<Moose> library name is a special shortcut for L<MooseX::Types::Moose>.
 
 =head2 Generator methods you can overload
 
@@ -233,8 +258,7 @@ L<MooseX::Types::Moose>.
 
 =item type_export_generator( $short, $full )
 
-Creates a closure returning the type's L<Moose::Meta::TypeConstraint> 
-object. 
+Creates a closure returning the type's L<Moose::Meta::TypeConstraint> object.
 
 =item check_export_generator( $short, $full, $undef_message )
 
@@ -265,25 +289,20 @@ type does not yet exist.
 
 =back
 
-=head1 NOTES REGARDING PARAMETERIZED CONSTRAINTS
+=head1 RECURSIVE SUBTYPES
 
-L<MooseX::Types> uses L<MooseX::Types::TypeDecorator> to do some overloading
-which generally allows you to easily create types with parameters such as:
+As of version 0.08, L<Moose::Types> has experimental support for Recursive
+subtypes.  This will allow:
 
-    subtype ParameterType,
-      as (ArrayRef[Int]);
+    subtype Tree() => as HashRef[Str|Tree];
 
-However, due to an outstanding issue you will need to wrap the parameterized
-type inside parenthesis, as in the example above.  Hopefully this limitation
-will be lifted in a future version of this module.
+Which validates things like:
 
-If you are using paramterized types in the options section of an attribute
-declaration, the parenthesis are not needed:
+    {key=>'value'};
+    {key=>{subkey1=>'value', subkey2=>'value'}}
 
-    use Moose;
-    use MooseX::Types::Moose qw(HashRef Int);
-    
-    has 'attr' => (isa=>HashRef[Str]);
+And so on.  This feature is new and there may be lurking bugs so don't be afraid
+to hunt me down with patches and test cases if you have trouble.
 
 =head1 NOTES REGARDING TYPE UNIONS
 
@@ -291,33 +310,32 @@ L<MooseX::Types> uses L<MooseX::Types::TypeDecorator> to do some overloading
 which generally allows you to easily create union types:
 
   subtype StrOrArrayRef,
-    as Str|ArrayRef;    
+      as Str|ArrayRef;
 
 As with parameterized constrains, this overloading extends to modules using the
 types you define in a type library.
 
-    use Moose;
-    use MooseX::Types::Moose qw(HashRef Int);
-    
-    has 'attr' => (isa=>HashRef|Int);
+  use Moose;
+  use MooseX::Types::Moose qw(HashRef Int);
+
+  has 'attr' => ( isa => HashRef | Int );
 
 And everything should just work as you'd think.
-    
+
 =head1 METHODS
 
 =head2 import
 
-Installs the L<MooseX::Types::Base> class into the caller and 
-exports types according to the specification described in 
-L</"LIBRARY DEFINITION">. This will continue to 
-L<Moose::Util::TypeConstraints>' C<import> method to export helper
-functions you will need to declare your types.
+Installs the L<MooseX::Types::Base> class into the caller and exports types
+according to the specification described in L</"LIBRARY DEFINITION">. This
+will continue to L<Moose::Util::TypeConstraints>' C<import> method to export
+helper functions you will need to declare your types.
 
 =cut
 
 sub import {
     my ($class, %args) = @_;
-    my  $callee = caller;
+    my  $caller = caller;
 
     # everyone should want this
     strict->import;
@@ -325,7 +343,7 @@ sub import {
 
     # inject base class into new library
     {   no strict 'refs';
-        unshift @{ $callee . '::ISA' }, 'MooseX::Types::Base';
+        unshift @{ $caller . '::ISA' }, 'MooseX::Types::Base';
     }
 
     # generate predeclared type helpers
@@ -339,62 +357,98 @@ sub import {
                 if $type =~ /::/;
 
             # add type to library and remember to export
-            $callee->add_type($type);
+            $caller->add_type($type);
             push @to_export, $type;
         }
 
-        $callee->import({ -full => 1, -into => $callee }, @to_export);
+        $caller->import({ -full => 1, -into => $caller }, @to_export);
     }
 
     # run type constraints import
-    return Moose::Util::TypeConstraints->import({ into => $callee });
+    Moose::Util::TypeConstraints->import({ into => $caller });
+
+    # override some with versions that check for syntax errors
+    MooseX::Types::CheckedUtilExports->import({ into => $caller });
+
+    1;
 }
 
 =head2 type_export_generator
 
 Generate a type export, e.g. C<Int()>. This will return either a
 L<Moose::Meta::TypeConstraint> object, or alternatively a
-L<MooseX::Types::UndefinedType> object if the type was not
-yet defined.
+L<MooseX::Types::UndefinedType> object if the type was not yet defined.
 
 =cut
 
 sub type_export_generator {
     my ($class, $type, $name) = @_;
-    return sub {
-        my $type_constraint;
+    
+    ## Return an anonymous subroutine that will generate the proxied type
+    ## constraint for you.
+
+    return subname "__TYPE__::$name" => sub {
+        my $type_constraint = $class->create_base_type_constraint($name);
+
         if(defined(my $params = shift @_)) {
-            if(ref $params eq 'ARRAY') {
+            ## We currently only allow a TC to accept a single, ArrayRef
+            ## parameter, as in HashRef[Int], where [Int] is what's inside the
+            ## ArrayRef passed.
+            if(reftype $params eq 'ARRAY') {
                 $type_constraint = $class->create_arged_type_constraint($name, @$params);
+            } elsif(!defined $type_constraint) {
+                croak "Syntax error in type definition (did you forget a comma"
+                    . " after $type?)";
             } else {
-                croak 'Arguments must be an ArrayRef, not '. ref $params;
+                croak "Argument must be an ArrayRef to create a parameterized "
+                    . "type, Eg.: ${type}[Int]. Got: ".ref($params)."."
             }
-        } else {
-            $type_constraint = $class->create_base_type_constraint($name);
         }
+
         $type_constraint = defined($type_constraint) ? $type_constraint
          : MooseX::Types::UndefinedType->new($name);
          
-        return $class->create_type_decorator($type_constraint);
+        my $type_decorator = $class->create_type_decorator($type_constraint);
+        
+        ## If there are additional args, that means it's probably stuff that
+        ## needs to be returned to the subtype.  Not an ideal solution here but
+        ## doesn't seem to cause trouble.
         
+        if(@_) {
+            return ($type_decorator, @_);
+        } else {
+            return $type_decorator;
+        }
     };
 }
 
 =head2 create_arged_type_constraint ($name, @args)
 
-Given a String $name with @args find the matching typeconstraint.
+Given a String $name with @args find the matching typeconstraint and parameterize
+it with @args.
 
 =cut
 
 sub create_arged_type_constraint {
-    my ($class, $name, @args) = @_;
-    my $type_constraint = Moose::Util::TypeConstraints::find_or_create_type_constraint($name);
-       return $type_constraint->parameterize(@args)
+    my ($class, $name, @args) = @_;  
+    my $type_constraint = Moose::Util::TypeConstraints::find_or_create_type_constraint("$name");
+    my $parameterized = $type_constraint->parameterize(@args);
+    # It's obnoxious to have to parameterize before looking for the TC, but the
+    # alternative is to hard-code the assumption that the name is
+    # "$name[$args[0]]", which would be worse.
+    # This breaks MXMS, unfortunately, which relies on things like Tuple[...]
+    # creating new type objects each time.
+    # if (my $existing =
+    #     Moose::Util::TypeConstraints::find_type_constraint($parameterized->name)) {
+    #     return $existing;
+    # }
+    # Moose::Util::TypeConstraints::register_type_constraint($parameterized);
+    return $parameterized;
 }
 
 =head2 create_base_type_constraint ($name)
 
-Given a String $name, find the matching typeconstraint.
+Given a String $name, find the matching type constraint.
 
 =cut
 
@@ -417,7 +471,7 @@ sub create_type_decorator {
 
 =head2 coercion_export_generator
 
-This generates a coercion handler function, e.g. C<to_Int($value)>. 
+This generates a coercion handler function, e.g. C<to_Int($value)>.
 
 =cut
 
@@ -457,29 +511,98 @@ sub check_export_generator {
 
 =head1 CAVEATS
 
+The following are lists of gotchas and their workarounds for developers coming
+from the standard string based type constraint names
+
+=head2 Uniqueness
+
 A library makes the types quasi-unique by prefixing their names with (by
 default) the library package name. If you're only using the type handler
 functions provided by MooseX::Types, you shouldn't ever have to use
 a type's actual full name.
 
+=head2 Argument separation ('=>' versus ',')
+
+The L<perlop> manpage has this to say about the '=>' operator: "The => operator is
+a synonym for the comma, but forces any word (consisting entirely of word
+characters) to its left to be interpreted as a string (as of 5.001). This
+includes words that might otherwise be considered a constant or function call."
+
+Due to this stringification, the following will NOT work as you might think:
+
+  subtype StrOrArrayRef => as Str | ArrayRef;
+
+The 'StrOrArrayRef' will have its stringification activated this causes the
+subtype to not be created.  Since the bareword type constraints are not strings
+you really should not try to treat them that way.  You will have to use the ','
+operator instead.  The author's of this package realize that all the L<Moose>
+documention and examples nearly uniformly use the '=>' version of the comma
+operator and this could be an issue if you are converting code.
+
+Patches welcome for discussion.
+
+=head2 Compatibility with Sub::Exporter
+
+If you want to use L<Sub::Exporter> with a Type Library, you need to make sure
+you export all the type constraints declared AS WELL AS any additional export
+targets. For example if you do:
+
+  package TypeAndSubExporter;
+
+  use MooseX::Types::Moose qw(Str);
+  use MooseX::Types -declare => [qw(MyStr)];
+  use Sub::Exporter -setup => { exports => [qw(something)] };
+
+  subtype MyStr, as Str;
+
+  sub something {
+      return 1;
+  }
+
+  # then in another module ...
+
+  package Foo;
+  use TypeAndSubExporter qw(MyStr);
+
+You'll get a '"MyStr" is not exported by the TypeAndSubExporter module' error.
+Upi can workaround by:
+
+  - use Sub::Exporter -setup => { exports => [ qw(something) ] };
+  + use Sub::Exporter -setup => { exports => [ qw(something MyStr) ] };
+
+This is a workaround and I am exploring how to make these modules work better
+together.  I realize this workaround will lead a lot of duplication in your
+export declarations and will be onerous for large type libraries.  Patches and
+detailed test cases welcome. See the tests directory for a start on this.
+
+=head1 COMBINING TYPE LIBRARIES
+
+You may want to combine a set of types for your application with other type
+libraries, like L<MooseX::Types::Moose> or L<MooseX::Types::Common::String>.
+
+The L<MooseX::Types::Combine> module provides a simple API for combining a set
+of type libraries together.
+
 =head1 SEE ALSO
 
-L<Moose>, 
-L<Moose::Util::TypeConstraints>, 
-L<MooseX::Types::Moose>,
+L<Moose>, L<Moose::Util::TypeConstraints>, L<MooseX::Types::Moose>,
 L<Sub::Exporter>
 
-=head1 AUTHOR AND COPYRIGHT
+=head1 ACKNOWLEDGEMENTS
+
+Many thanks to the C<#moose> cabal on C<irc.perl.org>.
+
+=head1 CONTRIBUTORS
+
+jnapiorkowski: John Napiorkowski <jjnapiork@cpan.org>
 
-Robert 'phaylon' Sedlacek C<E<lt>rs@474.atE<gt>>, with many thanks to
-the C<#moose> cabal on C<irc.perl.org>.
+caelum: Rafael Kitover <rkitover@cpan.org>
 
-Additional features by John Napiorkowski (jnapiorkowski) <jjnapiork@cpan.org>.
+rafl: Florian Ragwitz <rafl@debian.org>
 
-=head1 LICENSE
+hdp: Hans Dieter Pearcey <hdp@cpan.org>
 
-This program is free software; you can redistribute it and/or modify
-it under the same terms as perl itself.
+autarch: Dave Rolsky <autarch@urth.org>
 
 =cut