Exploring Type::Tiny Part 4: Using Types::Standard as a Ref::Util-Like Library

This is an old article. Some links may be broken. Some of the information may no longer be correct. Opinions expressed in this article may no longer be held.

Type::Tiny is probably best known as a way of having Moose-like type constraints in Moo, but it can be used for so much more. This is the third in a series of posts showing other things you can use Type::Tiny for. This article along with part 1, part 2, and part 3 can be found on my blog and in the Cool Uses for Perl section of PerlMonks.

Even if you read the documentation of Types::Standard pretty thoroughly, you’d probably miss that you can do things like this:

   use Types::Standard qw(is_ArrayRef is_HashRef);
   
   if (is_ArrayRef($var)) {
      ...;
   }
   elsif (is_HashRef($var)) {
      ...;
   }

It is documented that Types::Standard exports functions called ArrayRef and HashRef, which are constant-like functions returning Moose/Moo-compatible type constraint objects, but where did these is_ArrayRef and is_HashRef functions come from?

Well, their existence is documented in Type::Library, the type library base class used by Types::Standard. Any type library built with it will offer is_* variants of type constraints. These functions check their argument and return a boolean indicating whether it passes the type constraint.

The object-oriented way of writing these checks is like this:

   use Types::Standard qw(ArrayRef HashRef);
   
   if (ArrayRef->check($var)) {
      ...;
   }
   elsif (HashRef->check($var)) {
      ...;
   }

Though the object-oriented way is a little slower because it will result in at least three sub calls (including a method call).

The is_* functions should be pretty darn fast, especially if Type::Tiny::XS is installed. Ref::Util::XS is faster, and Params::Util is sometimes faster, but using Type::Library-based type libraries (such as Types::Standard, Types::Common::Numeric, Types::Common::String, Types::Path::Tiny, Types::XSD, etc) will give you a richer selection of types that you can check.

Assertions

A common use for type checking functions is to do something like:

   is_ArrayRef($var) or die(...);

Type::Library-based type libraries offer a shortcut for this:

   assert_ArrayRef($var);

The return value of the assert_* functions (if they don’t die) is the parameter you passed to them, which makes it convenient to do things like:

   use Types::Standard qw(assert_Object assert_ArrayRef);
   
   sub process_data {
      my $self  = assert_Object( $_[0] );
      my $data  = assert_ArrayRef( $_[1] );
      ...;
   }

The object-oriented equivalent of assert_Object($thing) is Object->assert_return($thing). Due to overloading Object->($thing) will also work.

Coercions

If a type constraint has coercions (like Path from Types::Path::Tiny), there’s also a to_* function:

   use Types::Path::Tiny qw( to_Path );
   
   my $path = to_Path($thing);

Note that if a coercion fails, there is no exception thrown, and the original value is passed through unaltered. If you want to make sure coercion succeeded:

   use Types::Path::Tiny qw( assert_Path to_Path );
   
   my $path = assert_Path( to_Path($thing) );

The object-oriented equivalent of to_Path($thing) is Path->coerce($thing). The object-oriented equivalent of assert_Path(to_Path($thing)) is Path->assert_coerce($thing).

Parameterized Types

It would be pretty cool if you could do:

   if (is_ArrayRef[Int]($var)) {
      ...;
   }

But that wouldn’t be syntactically valid Perl.

You can do this though:

   use Types::Standard qw(ArrayRef Int);
   
   BEGIN {
      my $type = ArrayRef->of(Int);
      *is_ArrayRef_of_Int     = $type->compiled_check;
      *assert_ArrayRef_of_Int = \&{ $type };
      *to_ArrayRef_of_Int     = sub { $type->coerce(@_) };
   }
   
   if (is_ArrayRef_of_Int($var)) {
      ...;
   }

Exporting Tricks

To export just Object:

   use Types::Standard qw(Object);

To export just is_Object:

   use Types::Standard qw(is_Object);

To export Object and is_Object:

   use Types::Standard qw(Object is_Object);

To export Object and all related functions (is_Object, assert_Object, and to_Object):

   use Types::Standard qw(+Object);

To export Object, ArrayRef, and all the other types:

   use Types::Standard qw(:types);

To export Object, ArrayRef, all the other types, and the related is_* functions:

   use Types::Standard qw(:types :is);

To export Object, ArrayRef, all the other types, and the related is_*, assert_*, and to_* functions:

   use Types::Standard qw(:types :is :assert :to);