DEV Community

Toby Inkster
Toby Inkster

Posted on

3

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.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more