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.
Top comments (0)