These toolkits are... well... hmm... okay... they exist.
If you are starting a new project, there's very little reason not to use Class::Tiny, Moo, or Moose. So you're probably okay to skip this part of the fine manual and go straight to Type::Tiny::Manual::UsingWithTestMore.
package Person { use Class::InsideOut qw( public ); use Types::Standard qw( Str Int ); use Types::Common::Numeric qw( PositiveInt ); use Type::Params qw( signature ); # Type checks are really easy. # Just supply the type as a set hook. public name => my %_name, { set_hook => Str, }; # Define a type that silently coerces negative values # to positive. It's silly, but it works as an example! my $Years = PositiveInt->plus_coercions(Int, q{ abs($_) }); # Coercions are more annoying, but possible. public age => my %_age, { set_hook => sub { $_ = $Years->assert_coerce($_) }, }; # Parameter checking for methods is as expected. sub get_older { state $check = signature( method => 1, positional => [ $Years ] ); my ( $self, $years ) = $check->( @_ ); $self->_set_age( $self->age + $years ); } }
Param::Check example:
my $tmpl = { name => { allow => Str->compiled_check }, age => { allow => Int->compiled_check }, }; check($tmpl, { name => "Bob", age => 32 }) or die Params::Check::last_error();
Object::Accessor example:
my $obj = Object::Accessor->new; $obj->mk_accessors( { name => Str->compiled_check }, { age => Int->compiled_check }, );
Caveat: Object::Accessor doesn't die when a value fails to meet its type constraint; instead it outputs a warning to STDERR. This behaviour can be changed by setting "$Object::Accessor::FATAL = 1".
use Types::Standard -types; use Class::Struct; { my %MAP; my $orig_isa = \&UNIVERSAL::isa; *UNIVERSAL::isa = sub { return $MAP{$1}->check($_[0]) if $_[1] =~ /^CLASSSTRUCT::TYPETINY::(.+)$/ && exists $MAP{$1}; goto $orig; }; my $orig_dn = \&Type::Tiny::display_name; *Type::Tiny::display_name = sub { if (caller(1) eq 'Class::Struct') { $MAP{$_[0]{uniq}} = $_[0]; return "CLASSSTRUCT::TYPETINY::".$_[0]{uniq}; } goto $orig_dn; }; } struct Person => [ name => Str, age => Int ]; my $bob = Person->new( name => "Bob", age => 21, ); $bob->name("Robert"); # okay $bob->name([]); # dies
If any of your accessors are ":rw" then you would also need to add type checks to those.
use Class::Plain; class Point { use Types::Common -types, -sigs; field x :reader; field y :reader; signature_for new => ( method => !!1, bless => !!0, named => [ x => Int, y => Int, ], ); method as_arrayref () { return [ $self->x, $self->y ]; } }
The following signature may also be of interest:
signature_for new => ( method => !!1, multiple => [ { named => [ x => Int, y => Int, ], bless => !!0, }, { positional => [ Int, Int ], goto_next => sub { my ( $class, $x, $y ) = @_; return ( $class, { x => $x, y => $y } ), }, }, ], );
This would allow your class to be instantiated using any of the following:
my $point11 = Point->new( { x => 1, y => 1 } ); my $point22 = Point->new( x => 2, y => 2 ); my $point33 = Point->new( 3, 3 );
Type::Tiny for test suites.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.