Type::Tiny v2 is Coming
Tags: perl, news, announcements
Published: 2022-09-16 15:11:07

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:

```perl
   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:

```perl
   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:

```perl
   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:

```perl
   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.

```perl
   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:

```perl
   use Type::Tiny::Enum Size => [ qw( S M L XL ) ];
```

You can use this in a class like:

```perl
   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:

```perl
   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:

```perl
   use Types::Common -all;
```

Though like always, you can list imports explicitly:

```perl
   use Types::Common qw( signature_for Num NonEmptyStr );
```

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

```perl
   use Types::Common -lexical, -all;
```

## A type divided against itself shall stand

You can now divide a type constraint by another:

```perl
   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:

```perl
   has output_list => (
      is        => 'ro',
      isa       => ArrayRef,
      default   => sub { [] },
   );
```

You can now write this:

```perl
   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:

```perl
   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.