DEV Community

Toby Inkster
Toby Inkster

Posted on

Type::Tiny v2 is Coming

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;
   }
Enter fullscreen mode Exit fullscreen mode

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;
   }
Enter fullscreen mode Exit fullscreen mode

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;
   }
Enter fullscreen mode Exit fullscreen mode

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;
   }
Enter fullscreen mode Exit fullscreen mode

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;
   }
Enter fullscreen mode Exit fullscreen mode

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 ) ];
Enter fullscreen mode Exit fullscreen mode

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;
         }
      }
   }
Enter fullscreen mode Exit fullscreen mode

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 );
   }
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Though like always, you can list imports explicitly:

   use Types::Common qw( signature_for Num NonEmptyStr );
Enter fullscreen mode Exit fullscreen mode

If you have a bleeding-edge Perl installed, you can import functions lexically:

   use Types::Common -lexical, -all;
Enter fullscreen mode Exit fullscreen mode

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 ],
   );
Enter fullscreen mode Exit fullscreen mode

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 { [] },
   );
Enter fullscreen mode Exit fullscreen mode

You can now write this:

   has output_list => (
      is        => 'ro',
      isa       => ArrayRef,
      default   => ArrayRef->type_default,
   );
Enter fullscreen mode Exit fullscreen mode

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;
      },
   );
Enter fullscreen mode Exit fullscreen mode

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)