Better Scala Tuples With shapeless


Many students in my Scala classes expect that Tuples will behave like collections by providing methods like head and tail, or providing access to elements by an index like Seq types provide. It's a reasonable expectation, but unfortunately they don't do this. By default we're limited to accessing the elements through methods like _1 and _2, and have none of the handy features of Seq. We can make this situation better with the addition of the shapeless library.

The standard Tuple has a type annotation for each element. A Tuple with a String and Int is typed as Tuple2[String, Int]. The first element in this Tuple2 must be a String while the second must be an Int. Shapeless provides a type called HList, which is a collection of heterogenous elements where each element in the collection has it's own type. Contrast this to the standard Scala collections where every element in the collection must be the same type. Conceptually, an HList with a String and Int could be thought of as HList[String, Int]. This is defining a collection that has two elements with a String first and an Int second. Does this sound kind of like a Tuple2? Shapeless provides a little syntax sugar that makes a standard Tuple behave like a collection.

Let's take a look at this in action! As usual, I will use sbt and the REPL to interactively explore Shapless. Launch sbt (or activator) and enter the following commands to bring in the library:

> set scalaVersion := "2.11.7"
> set libraryDependencies += "com.chuusai" %% "shapeless" % "2.2.4"

Then launch the Scala REPL with the console command:

> console
[info] Starting scala interpreter...
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

First, import the packages that we need:

scala> import shapeless._
import shapeless._
scala> import syntax.std.tuple._
import syntax.std.tuple._

The last import brings in the helpers for tuples. Let's create a Tuple to test with.

scala> val t1 = ("Scala", 1, true)
t1: (String, Int, Boolean) = (Scala,1,true)

This is a standard library Tuple3[String, Int, Boolean]. But with Shapeless we now have head and tail:

scala> t1.head
res2: String = Scala
scala> t1.tail
res3: (Int, Boolean) = (1,true)

We can access elements with the apply method just like Seqs:

scala> t1(0)
res6: String = Scala
scala> t1(1)
res7: Int = 1

And we can concatenate tuples with the usual operators:

scala> t1 :+ "yay"
res11: (String, Int, Boolean, String) = (Scala,1,true,yay)
scala> t1 ++ ("yay", 5.0)
res12: (String, Int, Boolean, String, Double) = (Scala,1,true,yay,5.0)

Making Tuples act like collections is just the tip of the Shapeless iceberg, but it's definitely a nice feature!