Eagle-eyed watchers of CPAN may have noticed that I’ve recently been releasing Type::Tiny development releases with version numbers 1.999_XYZ.
Type::Tiny v2 is intended to be compatible with Type::Tiny v1. If you’ve used Type::Tiny v1, you shouldn’t need to change any code, but Type::Tiny v2 has a few new features which may make your code simpler, more maintainable, and more readable if you adopt them.
Type::Params v2 API
Type::Params can be used to provide typed subroutine signatures:
use feature qw( state ); use Type::Params qw( compile ); use Types::Standard qw( Num ); sub add_numbers { state $signature = compile( Num, Num ); my ( $x, $y ) = $signature->( @_ ); return $x + $y; }
However, things like named paramaters, catering for $self
in methods, etc felt like afterthoughts. Here is how you’d write the same signature in version 1 as a method call using named parameters:
use feature qw( state ); use Type::Params qw( compile_named_oo ); use Types::Standard qw( Num ); sub add_numbers { state $signature = compile_named_oo( { head => [ Any ] }, 'x' => Num, 'y' => Num, ); my ( $self, $arg ) = $signature->( @_ ); return $arg->x + $arg->y; }
While the old API is still supported, Type::Params v2 has two new functions, signature
and signature_for
, which I feel provide a more powerful and more consistent interface.
signature
works much the same as compile
, but takes a top-level hash of options, allowing it to cater for both positional and named parameters.
Here is an example for positional parameters:
use feature qw( state ); use Type::Params qw( signature ); use Types::Standard qw( Num ); sub add_numbers { state $signature = signature( method => 0, positional => [ Num, Num ], ); my ( $x, $y ) = $signature->( @_ ); return $x + $y; }
Here is an example for named parameters:
use feature qw( state ); use Type::Params qw( signature ); use Types::Standard qw( Num ); sub add_numbers { state $signature = signature( method => 1, named => [ 'x' => Num, 'y' => Num ], ); my ( $self, $arg ) = $signature->( @_ ); return $arg->x + $arg->y; }
And signature_for
allows you to turn that definition inside-out.
use experimental qw( signatures ); use Type::Params qw( signature_for ); use Types::Standard qw( Num ); signature_for add_numbers => ( method => 1, named => [ 'x' => Num, 'y' => Num ], ); sub add_numbers ( $self, $arg ) { return $arg->x + $arg->y; }
Handy import shortcuts
A handy way to define an Enum type in Type::Tiny 2 is:
use Type::Tiny::Enum Size => [ qw( S M L XL ) ];
You can use this in a class like:
package Local::TShirt { use Moose; use Types::Common -types; use Type::Tiny::Enum Size => [ qw( S M L XL ) ]; use namespace::autoclean; has size => ( is => 'ro', isa => Size, required => 1, ); sub price { my $self = shift; my $size = $self->size; if ( $size eq SIZE_XL ) { return 10.99; } elsif ( $size eq SIZE_L ) { return 9.99; } else { return 8.99; } } }
Yes, Enum type constraints now provide constants like SIZE_XL
above.
Type::Tiny::Class provides a similar shortcut:
sub post_data ( $url, $data, $ua=undef ) { use Type::Tiny::Class -lexical, 'HTTP::Tiny'; $ua = HTTPTiny->new unless is_HTTPTiny $ua; $ua->post( $url, $data ); }
Type::Tiny::Role and Type::Tiny::Duck also provide shortcuts.
Types::Common
Having checked out a lot of modules which use Type::Tiny, I’ve noticed that the most common modules people import from are Types::Standard, Type::Params, Types::Common::Numeric, and Types::Common::String.
Types::Common is a new module that combines all of the above. For quick scripts and one-liners, something like this may save a bit of typing:
use Types::Common -all;
Though like always, you can list imports explicitly:
use Types::Common qw( signature_for Num NonEmptyStr );
If you have a bleeding-edge Perl installed, you can import functions lexically:
use Types::Common -lexical, -all;
A type divided against itself shall stand
You can now divide a type constraint by another:
has lucky_numbers => ( is => 'ro', isa => ArrayRef[ Num / Any ], );
What does this mean?
Under normal circumstances, Num/Any evaluates to just Any. Num is basically just documentation, so you’re documenting that lucky_numbers
is intended to be an arrayref of numbers, but as a speed boost, the attribute will just check that it’s an arrayref of anything.
When the EXTENDED_TESTING
environment variable is switched on though, Num/Any will evaluate to Num, so stricter type checks will kick in.
Type defaults
Instead of this:
has output_list => ( is => 'ro', isa => ArrayRef, default => sub { [] }, );
You can now write this:
has output_list => ( is => 'ro', isa => ArrayRef, default => ArrayRef->type_default, );
This is more typing, so why do this? Well, for ArrayRef it might be more typing, but in this case:
has colour_scheme => ( is => 'ro', isa => ColourScheme, default => sub { my %colours = ( foreground => 'black', background => 'white', links => 'blue', highlight => 'red', ); return \%colours; }, );
It might be neater to include the default in the definition of your ColourScheme type.
The new DelimitedStr type
Types::Common::String now has a DelimitedStr type.
This allows DelimitedStr[ “|”, Int ] to accept strings like "12|34|-99|0|1"
.
Internals
There have been numerous internal refactorings in Type::Tiny v2, so if you’re using Type::Tiny and its related modules in more unorthodox ways, it may be worth explicitly testing your code still runs on the new version.
However, I have taken care to avoid breaking any documented APIs. The vast majority of the Type:Tiny v1 test suite still passes with Type::Tiny v2, with test cases that inspect the exact text of error messages being the only real change.