Introducing Type::Tiny
Type::Tiny is a tiny (no non-core dependencies) framework for building type constraints. OK, probably not that exciting. How can I grab your attention?
Rate WithMoose WithMooseAndTypeTiny WithMoose 8071/s -- -25% WithMooseAndTypeTiny 10778/s 34% --
The benchmark script is shown later so you can check I'm not doing anything hideously unfair to disadvantage Moose.
How can I hold your attention?
Type constraint libraries built with Type::Tiny work with Moose, Mouse and Moo! And because it's such a lightweight framework, with no dependencies on heavy metaobject protocols, it even becomes appealing to use in situations where you might not otherwise consider using a type constraint library at all.
Type::Tiny comes bundled with a number of other modules that help round out the framework, including:
- Type::Library - a base class for collections of type constraints
- Type::Utils - syntactic sugar for declaring types
- Types::Standard - a library of commonly used types
- Test::TypeTiny - simple functions for testing type constraints work
Type Constraints?
Let's get back to basics... what's a type constraint library? If you're writing anything more than a quick throwaway script, you generally need to do a bit of data validation. Your array_sum
function might need to check that it gets passed an arrayref and all the values in the array are numeric.
In another part of the code your delete_articles_by_id
function might also need to accept an array of numeric values. Two checks for arrayrefs of numbers, in different parts of your codebase. The principle of DRY says that you should factor both of these checks out to a single place in your code.
Once you've factored all of these checks out into one place, that's your type constraint library.
Building a Type Library with Type::Library
Let's say we want to build a "natural numbers" type constraint. Natural numbers are the positive integers plus zero. (The inclusion of zero is contentious in some circles, but we'll put that aside for now.) It helps that Types::Standard defines an Int
type constraint, so rather than starting from scratch, we can refine that.
package MyApp::Types; use base "Type::Library"; # type libraries must inherit from this use Type::Utils; # sugar for declaring type constraints use Types::Standard qw(Int); declare "NaturalNum", as Int, where { $_ >= 0 }; 1; # magic true value
That was easy. Now within our application we can:
use MyApp::Types qw(NaturalNum);
And this will export NaturalNum
as a "constant". The constant returns an object that we can call methods on, so:
NaturalNum->check($value); # returns true or false NaturalNum->assert_valid($value); # returns true or dies
The constant can also be used directly within Moo or Moose attribute declarations:
has message_count => (is => "ro", isa => NaturalNum, required => 1);
Coercions
A next step is to define coercions. Within our type constraint library we can add:
use Types::Standard qw( Num ArrayRef ); coerce "NaturalNum", from Num, via { int(abs($_)) }, from ArrayRef, via { scalar(@$_) };
Now within our application we can:
use MyApp::Types qw(to_NaturalNum); my $goats = ["Alice Gruff", "Bob Gruff", "Carol Gruff"]; say to_NaturalNum($goats); # say 3
Coercions can be used within Moose attribute definitions:
has message_count => ( is => "ro", isa => NaturalNum, required => 1, coerce => 1, );
Or Moo attribute definitions:
has message_count => ( is => "ro", isa => NaturalNum, required => 1, coerce => NaturalNum->coercion, # spot the difference );
Coercions are a useful feature, and there are planned additions to Type::Coercion and Type::Library to make them even better in the future.
* * *
Anyway, I hope this provides a brief summary of Type::Tiny's features, and maybe tempts you to try it out. Keep an eye out for future articles on topics such as optimizing type constraints, and coercion power features.
Appendix
Here's the benchmarking script as promised...
package main; use strict; use warnings; use Benchmark qw(cmpthese); { package Class::WithMoose; use Moose; has attr => (is => "ro", isa => "ArrayRef[Int]"); __PACKAGE__->meta->make_immutable; } { package Class::WithMooseAndTypeTiny; use Moose; use Types::Standard -all; has attr => (is => "ro", isa => ArrayRef[Int]); __PACKAGE__->meta->make_immutable; } our %data = ( attr => [1 .. 20] ); cmpthese(-1, { WithMoose => q{ Class::WithMoose->new(%::data) }, WithMooseAndTypeTiny => q{ Class::WithMooseAndTypeTiny->new(%::data) }, });
I wonder if these will work with Test::Deep::Type?
I've had a quick try, and yes, it seems to.
The only major Moose-has-this feature missing from Type::Tiny is that Type::Tiny doesn't have a type registry, which means there's no global mechanism for looking up a type constraint from its name.
(Not that Test::Deep::Type attempts to look up types by name anyway!)
Cool!
I think there's a feeling now that a global type registry is a bad idea, or at least has enough drawbacks that it might not always be a good idea -- different modules can stomp on each other by altering the global registry. Certainly the approach MooseX::Types has taken (that you are following here), where each type must be referred to directly (e.g. via an imported sub that returns the type object) has proven to work out well.
What would be even better is if type objects were immutable, to prevent similar stomping (e.g. someone adding a coercion to an existing type) -- so if one wants to make modifications, one has to create a *new* object, rather than modifying an existing type that an imported sub returns.
Type::Tiny constraints are immutable, but coercions are not.
There is an implementation of immutable coercions in the repo, but it didn't land until after the 0.001 "feature freeze". First stable release with immutable coercions is likely to be 0.004, but the 0.003_01 and 0.003_02 releases on CPAN already have it. Even once this is in place coercions are mutable by default - you need to call
$type->coercion->freeze
to freeze them; howeverfreeze
is automatically called when certain events occur, such as inflating a Type::Tiny object to a Moose::Meta::TypeConstraint.Type::Tiny also has a
plus_coercions
method to hopefully steer people towards the "create a new constraint" technique by simply making it so convenient.All my +1s. This is awesome. Thanks for sharing!