Many students in my Scala classes expect that Tuple
s 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... [info] 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. scala>
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 Seq
s:
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!