Type::Tiny is probably best known as a way of having Moose-like type constraints in Moo, but it can be used for so much more. This is the third in a series of posts showing other things you can use Type::Tiny for. This article along with part 1 and part 2 can be found on my blog and in the Cool Uses for Perl section of PerlMonks.

This works:

   use Types::Standard qw(Int);
   
   tie(my @numbers, Int);
   
   push @numbers, 1, 2, 3;     # ok
   push @numbers, "four";      # dies

Well, if you try it, you may find it complains about not being able to load Type::Tie.

Type::Tie is an add-on for Type::Tiny distributed separately. It's an optional dependency, so if you want to use this feature, you'll need to make sure it's installed.

Coercions

This tie feature automatically supports coercions.

   use Types::Standard qw(Int Num);
   
   my $RoundedInt = Int->plus_coercions( Num, 'int $_' ); 
   tie(my @numbers, $RoundedInt);
   
   push @numbers, 1, 2, 3;     # ok
   push @numbers, 4.2;         # rounded to 4
   push @numbers, "five";      # dies

More about Type::Tie

Type::Tie is designed to be pretty independent of Type::Tiny. You can use it with MooseX::Types, Mouse::Types, and Specio, and it also bundles its own nanoscale type constraint library Type::Nano.

   use Type::Tie qw();
   use MooseX::Types::Moose qw(Int);
   
   tie(my @numbers, "Type::Tie::ARRAY", Int);

To save yourself typing "Type::Tie::ARRAY", "Type::Tie::HASH", and "Type::Tie::SCALAR" all the time, Type::Tie offers a convenience function ttie:

   use Type::Tie qw(ttie);
   use MooseX::Types::Moose qw(Int);
   
   ttie(my @numbers, Int);

Use in Attributes

Perl has a type checking hole thanks to references:

   use v5.16;
   
   package Foo {
      use Moo;
      use Types::Standard qw(ArrayRef Int);
      has numbers => (
         required => 1,
         is       => 'ro',
         isa      => ArrayRef[Int],
      );
   }
   
   my $foo = Foo->new( numbers => [1, 2, 3] );
   
   push @{ $foo->numbers }, "hi";   # this is allowed

The type constraint is only checked in the constructor and in writers/accessors.

Tying the array allows you to perform type checks and coercions on any new elements added to the array. It's a use for trigger that doesn't suck!

   use v5.16;
   
   package Foo {
      use Moo;
      use Types::Standard qw(ArrayRef Int);
      has numbers => (
         required => 1,
         is       => 'ro',
         isa      => ArrayRef[Int],
         trigger  => sub { tie @{$_[1]}, Int },
      );
   }
   
   my $foo = Foo->new( numbers => [1, 2, 3] );
   
   push @{ $foo->numbers }, "hi";   # dies

With a little bit of work (okay, a lot!) it should be possible to even check deeply nested structures.

Performance

While effort has been made to optimize Type::Tie, tied variables are necessarily slower than untied ones.

If you have an array you want to make sure only contains integers, but you don't want to compromise on performance, you could enable the tie only when you run your test suite, and trust that your test suite will be enough to trigger any potential errors.

   use Types::Standard qw(Int);
   use Devel::StrictMode qw(STRICT);
   
   my @array_of_ints;
   tie @array_of_ints, Int if STRICT;
   
   ...;   # do stuff here

Devel::StrictMode is a module which exports a constant called STRICT which will be true if the PERL_STRICT, EXTENDED_TESTING, RELEASE_TESTING, or AUTHOR_TESTING environment variables is true, and false otherwise.