A Tiny But Powerful Type System

Type::Tiny - 2014-12-11

Perl doesn't (yet) have a native type system built into it, but as is often the case, powerful solutions can be found on the CPAN. Type::Tiny is a small, lightweight type system for Perl that is compatible with Moose, Moo and Mouse. It ships with a useful set of standard types, and the underpinnings you need to quickly define type libraries of your own.

A Moo Constraints Recap

Let's write a very simple Moo-based object representing what we'll be leaving out for jolly old St Nick this Christmas.

Accessors in Moo can be defined with a simple isa keyword that accepts a subroutine reference that can validate whatever you're setting the attribute to:

# how many mince pies are there?hasmincepies=>(is=>"rw",isa=>Int,default=>0,);

# how many carrots got left out?hascarrots=>(is=>"rw",isa=>Int,default=>0,);

# and a drinkhasdrink=>(is=>"rw",isa=>Enum["Milk","Sherry"],);

1;

You'll note that the type names themselves (Int, Enum, etc) are barewords, not quoted strings nor variables. Under the hood Type::Tiny is doing clever things with subroutine prototypes and overloaded variables to provide the clear syntax you see above, none of which you need worry about in your day to day usage of the type system.

Now if we attempt to define an invalid object

1: 2: 3: 4:

my$plate=StuffForSanta->new(carrots=>10,drink=>"Prune Juice",);

Our type system complains bitterly in a much more readable fashion when things go wrong:

Value "Prune Juice" did not pass type constraint "Enum["Milk","Sherry"]" (in $args->{"drink"}) at plate.pl line 8
"Enum["Milk","Sherry"]" requires that the value is equal to "Milk" or "Sherry"

Writing your Own Types

Writing your own type is very simple to do. You can use Type::Tiny's constructor, or simpler yet, use the syntactic sugar for declaring types from Type::Utils:

1: 2: 3: 4: 5:

useType::Utils-all;

declare"USTelephone",where{/^(?:[+]?1-)?\d{3}-?\d{3}-?\d{4}$/a},message{"$_ doesn't look like a US telephone number to me"};

Since declaring a type also defines the handy is_Whatever function call we can easily test the type:

1: 2:

okis_USTelephone("+1-202-456-1111"),"Whitehouse telephone number valid";okis_USTelephone("+7-495-695-37-76"),"Kremlin telephone number not valid";

Rather than declaring your types in the class that you're going to be using them, you should put them in their own package that subclasses Type::Libary:

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:

packageType::USTelephone;usebaseqw(Type::Library);

use5.014;usewarnings;

useType::Utils-all;

declare"USTelephone",where{/^(?:1-)?\d{3}-?\d{3}-?\d{4}$/a},message{"$_ doesn't look like a US telephone number to me"};

1;

Then that type can be used in your object class:

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:

packageAlbanyPMMember;

useMoo;useTypes::Standardqw(:all);useTypes::USTelephoneqw(:all);

hasname=>(is=>"ro",isa=>Str,);

hascell=>(is=>"ro",isa=>USTelephone,);

...

Coercion

Automatic coercion allows Perl to automatically convert one type from another type in situations where this might be beneficial.

One important thing to note that this coercion only has effect in this one accessor where we've set coercion - we don't have to worry about accidentally triggering a coercion in other accessors and having spooky action at a distance.

Per Accessor Moose Coercion

Up until this point our Moo and Moose code looks essentially identical, but the way Moose handles coercions is different to Moo: It has global coercions that apply any time a type is used in any accessor that has coercions enabled. This can lead to unpredictable action at a distance if we're not careful.

Type::Tiny has a solution to this issue; Essentially it has a syntax for creating a one-off variant of a standard class with additional coercion ability.

The Color->plus_coercions(ColorFromStr) call actually returns a new one-off type that is a Color class with the additional ColorFromStr coercion.

Speed Optimizations

Because type systems often end up in hot areas of code, every little speed improvement can help. Up until now we've been declaring our code as simple subroutines, but we can optimize this further by giving our type system a string containing a snippet of code allowing the type system to directly compile this into the accessor routine.

To demonstrate how much quicker this can be, let's do a quick benchmark. First let's declare a simple type class:

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:

packageMyTyps;usebaseqw(Type::Library);

use5.014;usewarnings;

useType::Utils-all;

declare"ThreeChars",where{defined&&!ref&&length==3},message{"Not a three character string"};

Conclusion

Type::Tiny offers a deceptively simple approach to creating types with Perl. Despite its ::Tiny name, it is a powerful system that allows you do do complex validation. And unlike Moose-based type systems, Type::Tiny's entire dependency chain involves only one Perl module (which is 100% Pure Perl) and does not require a compiler to build making it an excellent choice for code that needs to be easy to distribute.