Type::Params - Params::Validate-like parameter validation using Type::Tiny type constraints and coercions
use v5.10; use strict; use warnings; use Type::Params qw( compile ); use Types::Standard qw( slurpy Str ArrayRef Num ); sub deposit_monies { state $check = compile( Str, Str, slurpy ArrayRef[Num] ); my ($sort_code, $account_number, $monies) = $check->(@_); my $account = Local::BankAccount->new($sort_code, $account_number); $account->deposit($_) for @$monies; } deposit_monies("12-34-56", "11223344", 1.2, 3, 99.99);
Type::Params uses Type::Tiny constraints to validate the parameters to a sub. It takes the slightly unorthodox approach of separating validation into two stages:
Compiling the parameter specification into a coderef; then
Using the coderef to validate parameters.
The first stage is slow (it might take a couple of milliseconds), but you only need to do it the first time the sub is called. The second stage is fast; according to my benchmarks faster even than the XS version of Params::Validate.
If you're using a modern version of Perl, you can use the state keyword which was a feature added to Perl in 5.10. If you're stuck on Perl 5.8, the example from the SYNOPSIS could be rewritten as:
state
my $deposit_monies_check; sub deposit_monies { $deposit_monies_check ||= compile( Str, Str, slurpy ArrayRef[Num] ); my ($sort_code, $account_number, $monies) = $check->(@_); ...; }
Not quite as neat, but not awful either.
There's a shortcut reducing it to one step:
use Type::Params qw( validate ); sub deposit_monies { my ($sort_code, $account_number, $monies) = validate( \@_, Str, Str, slurpy ArrayRef[Num] ); ...; }
Type::Params has a few tricks up its sleeve to make sure performance doesn't suffer too much with the shortcut, but it's never going to be as fast as the two stage compile/execute.
sub nth_root { state $check = compile( Num, Num ); my ($x, $n) = $check->(@_); return $x ** (1 / $n); }
Type::Params exports an additional keyword Invocant on request. This is a type constraint accepting blessed objects and also class names.
Invocant
use Types::Standard qw( ClassName Object Str Int ); use Type::Params qw( compile Invocant ); # a class method sub new_from_json { state $check = compile( ClassName, Str ); my ($class, $json) = $check->(@_); $class->new( from_json($json) ); } # an object method sub dump { state $check = compile( Object, Int ); my ($self, $limit) = $check->(@_); local $Data::Dumper::Maxdepth = $limit; print Data::Dumper::Dumper($self); } # can be called as either and object or class method sub run { state $check = compile( Invocant ); my ($proto) = $check->(@_); my $self = ref($proto) ? $proto : $default_instance; $self->_run; }
use Types::Standard qw( Object Optional Int ); sub dump { state $check = compile( Object, Optional[Int] ); my ($self, $limit) = $check->(@_); $limit //= 0; local $Data::Dumper::Maxdepth = $limit; print Data::Dumper::Dumper($self); } $obj->dump(1); # ok $obj->dump(); # ok $obj->dump(undef); # dies
use Types::Standard qw( slurpy ClassName HashRef ); sub new { state $check = compile( ClassName, slurpy HashRef ); my ($class, $ref) = $check->(@_); bless $ref => $class; } __PACKAGE__->new(foo => 1, bar => 2);
The following types from Types::Standard can be made slurpy: ArrayRef, Tuple, HashRef, Map, Dict. Hash-like types will die if an odd number of elements are slurped in.
ArrayRef
Tuple
HashRef
Map
Dict
A check may only have one slurpy parameter, and it must be the last parameter.
Just use a slurpy Dict:
use Types::Standard qw( slurpy Dict Ref Optional Int ); sub dump { state $check = compile( slurpy Dict[ var => Ref, limit => Optional[Int], ], ); my ($arg) = $check->(@_); local $Data::Dumper::Maxdepth = $arg->{limit}; print Data::Dumper::Dumper($arg->{var}); } dump(var => $foo, limit => 1); # ok dump(var => $foo); # ok dump(limit => 1); # dies
use Types::Standard qw( slurpy Dict Ref Optional Int ); sub my_print { state $check = compile( Str, slurpy Dict[ colour => Optional[Str], size => Optional[Int], ], ); my ($string, $arg) = $check->(@_); } my_print("Hello World", colour => "blue");
Coercions will automatically be applied for all type constraints that have a coercion associated.
use Type::Utils; use Types::Standard qw( Int Num ); my $RoundedInt = declare as Int; coerce $RoundedInt, from Num, q{ int($_) }; sub set_age { state $check = compile( Object, $RoundedInt ); my ($self, $age) = $check->(@_); $self->{age} = $age; } $obj->set_age(32.5); # ok; coerced to "32".
Coercions carry over into structured types such as ArrayRef automatically:
sub delete_articles { state $check = compile( Object, slurpy ArrayRef[$RoundedInt] ); my ($db, $articles) = $check->(@_); $db->select_article($_)->delete for @$articles; } # delete articles 1, 2 and 3 delete_articles($my_db, 1.1, 2.2, 3.3);
If type Foo has coercions from Str and ArrayRef and you want to prevent coercion, then use:
Foo
Str
state $check = compile( Foo->no_coercions );
Or if you just want to prevent coercion from Str, use:
state $check = compile( Foo->minus_coercions(Str) );
Or maybe add an extra coercion:
state $check = compile( Foo->plus_coercions(Int, q{ Foo->new_from_number($_) }), );
Note that the coercion is specified as a string of Perl code. This is usually the fastest way to do it, but a coderef is also accepted. Either way, the value to be coerced is $_.
$_
Type::Params is not really a drop-in replacement for Params::Validate; the API differs far too much to claim that. Yet it performs a similar task, so it makes sense to compare them.
Type::Params will tend to be faster if you've got a sub which is called repeatedly, but may be a little slower than Params::Validate for subs that are only called a few times. This is because it does a bunch of work the first time your sub is called to make subsequent calls a lot faster.
Type::Params is mostly geared towards positional parameters, while Params::Validate seems to be primarily aimed at named parameters. (Though either works for either.) Params::Validate doesn't appear to have a particularly natural way of validating a mix of positional and named parameters.
Type::Utils allows you to coerce parameters. For example, if you expect a Path::Tiny object, you could coerce it from a string.
Params::Validate allows you to supply defaults for missing parameters; Type::Params does not, but you may be able to use coercion from Undef.
If you are primarily writing object-oriented code, using Moose or similar, and you are using Type::Tiny type constraints for your attributes, then using Type::Params allows you to use the same constraints for method calls.
Type::Params comes bundled with Types::Standard, which provides a much richer vocabulary of types than the type validation constants that come with Params::Validate. For example, Types::Standard provides constraints like ArrayRef[Int] (an arrayref of integers), while the closest from Params::Validate is ARRAYREF, which you'd need to supplement with additional callbacks if you wanted to check that the arrayref contained integers.
ArrayRef[Int]
ARRAYREF
Whatsmore, Type::Params doesn't just work with Types::Standard, but also any other Type::Tiny type constraints.
Please report any bugs to http://rt.cpan.org/Dist/Display.html?Queue=Type-Tiny.
Type::Tiny, Type::Coercion, Types::Standard.
Toby Inkster <tobyink@cpan.org>.
This software is copyright (c) 2013 by Toby Inkster.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
To install Type::Tiny, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Type::Tiny
CPAN shell
perl -MCPAN -e shell install Type::Tiny
For more information on module installation, please visit the detailed CPAN module installation guide.