Type::Tiny Tricks #6: Tricks with Tuples

This is a very old article. It has been imported from older blogging software, and the formatting, images, etc may have been lost. Some links may be broken. Some of the information may no longer be correct. Opinions expressed in this article may no longer be held.

Let’s say you want an attribute to accept a pair of numbers – perhaps a geographic co-ordinates [ 50.873, -0.002 ]. You could constrain the attribute as ArrayRef[Num], but that would accept an arrayref containing a single number, or eight numbers, or even a reference to an empty array.

With the Tuple type constraint, you can be more exact in expressing which values are acceptable:

   isa  => Tuple[ Num, Num ]

Now let’s say we want to optionally accept a third number; an altitude, in metres, which should be an integer. If the altitude is unknown, then it may be omitted. That’s possible too:

   isa  => Tuple[ Num, Num, Optional[Int] ]

OK, let’s forget altitude now, and assume we want to accept a pair of co-ordinates followed by zero or more strings indicating a label for the location:

   [ 50.873, -0.002, "Lewes", "East Sussex", "United Kingdom" ]

The list of strings is variable length, but Tuples can cope with that too:

   isa  => Tuple[ Num, Num, slurpy ArrayRef[Str] ]

The slurpy part conceptually slurps the remaining items of the arrayref into a new temporary array, and validates that against the ArrayRef[Str] constraint. Checking a value against this constraint is roughly equivalent to:

   my ($temp1, $temp2, @slurpy) = @$value;
   Num->assert_valid($temp1);
   Num->assert_valid($temp2);
   ArrayRef->of(Str)->assert_valid(\@slurpy);

As well as validating arrayrefs containing a mixture of different datatypes, Tuple can be used to validate more homogenous arrays. For example Tuple[Int, slurpy ArrayRef[Int]] is equivalent to ArrayRef[Int] except that it requires at least one item in the array.

ArrayRef[Int] & Tuple[Any, slurpy Any] does the same, but might be more efficient because Any is very optimized.

What about if you want to accept an arrayref of integers which is typically many thousands of integers long. You might like to save time by only validating that the first five items in the arrayref are integers. The rest we’ll accept on trust:

   Tuple[ (Optional[Int]) x 5, slurpy Any ]

The Tuple type constraint is useful for all kinds of things. It would be useful to have an equivalently flexible constraint for hashrefs, wouldn’t it? We’ll cover that in the next tip.