Yesterday I was talking with a friend about Scala and the subject of tuples came up. We both had a bit of a laugh that neither of us was sure how to pronounce it, though we both leaned toward TUH-ple instead of TOO-ple. Anyway, the utility of tuples in Scala was not immediately apparent to him, so I thought I’d take a whack at explaining it here.
A Tuple in Scala is an immutable container used for storing two or more objects, possibly of different types. While a List or Array can only store objects that all have the same type, Tuples can store objects of any type. The most common use of tuples is when you have a method that needs to return more than one value, but creating a class for that return value is more trouble than it’s worth. It’s true that for same-type objects you could return a List, and for different-type objects you could return a List[Any], but both of these have downsides, which we’ll discuss.
Let’s look at a very contrived example. Let’s say you created a function that takes a string and returns the starting index of the first numbers if finds and the numbers themselves. That code might look like this
def reFind(str: String) = {
val re = """(d+)""".r
val m = re findFirstMatchIn str
m match {
case Some(m) => (m.start, str.substring(m.start, m.end))
case None => (0, "")
}
}
(I’ve removed any error checking for brevity.) You can see here that we’re creating a regular expression that looks for one or more numbers grouped together. We then match that against the passed-in string. The matching method returns a Some[Match], so we pattern match against that to see if we actually got a match. If we did, we create a tuple with the starting index of the match, and the match itself, and return it. If not, we return a tuple with 0 for the starting index and an empty string.
Calling this function looks like this
scala> val t = reFind("foo 123 bar")
t: (Int, java.lang.String) = (4,123)
You can see that what was returned is something with type (Int, java.lang.String); that’s actually an instance of Scala’s Tuple2 class. (There’s a synonym for Tuple2, called Pair.)
Now that we have this tuple, what do we do with it? If you want to access the values it contains, you do it in a way that might seem a bit strange at first. To get at the elements, you could do this
scala> val i = t._1
i: Int = 4
scala> val m = t._2
m: java.lang.String = 123
There are two things to point out here. First, unlike Lists and Arrays, you don’t use the () notation. You use a method consisting of an underscore and the index of the part you want. Second, unlike Lists and Arrays, tuples are 1-based instead of 0-based. (According to Programming In Scala, this is a nod to Haskell and ML.) Also notice the types of the vals you are assigning. That’s one of the benefits of using a Tuple instead of something like List[Any]; you still get compile-time type safety. Had you instead written the function like this
def reFind(str: String) = {
val re = """(d+)""".r
val m = re findFirstMatchIn str
m match {
case Some(m) => List[Any](m.start, str.substring(m.start, m.end))
case None => List[Any](0, "")
}
}
and called it, look what happens when you try to store the Int index in a local variable
scala> val l = reFind("foo 123 bar")
l: List[Any] = List(4, 123)
scala> val i: Int = l(0)
<console>:10: error: type mismatch;
found : Any
required: Int
val i: Int = l(0)
^
You would get a similar error trying to assign the String element to a local String val. That’s the major downside to using a List[Any]. (In the first example I used Scala’s type inference to set the types of the local variables; this time I wanted to be explicit to show the failure.)
As I mentioned earlier, you could define a class just to handle the return values of this function. There is nothing wrong with that solution, and some will find it superior to using a tuple, because you can assign meaningful names to the elements. You could define it like this
class ReResult(val index: Int, val part: String)
def reFind(str: String) = {
val re = """(d+)""".r
val m = re findFirstMatchIn str
m match {
case Some(m) => new ReResult(m.start, str.substring(m.start, m.end))
case None => new ReResult(0, "")
}
}
and call it like this
scala> val l = reFind("foo 123 bar")
l: ReResult = ReResult@57c52e72
scala> val i: Int = l.index
i: Int = 4
scala> val m: String = l.part
m: String = 123
If you think this is more maintainable, then by all means, use it. If you just want to easily return more than one value from a function, then consider using a tuple.
Another point on tuples is that you can assign all the elements of a tuple to local variables in a single step, rather than using multiple calls. So this is equivalent to all the assignments from the earlier examples
scala> val (i: Int, m: String) = l
i: Int = 4
m: String = 123
Depending on what you’re doing, this could be a useful way to get at the elements.
And one more thing. There are tuple classes that range from two elements all the way up to twenty-two. The classes are named Tuple2, Tuple3 … Tuple22. The () notation for creating tuples applies all the way up to twenty-two arguments, so you rarely need to actually use the class names. For example,
scala> val t = (23, "foo", 18.0)
t: (Int, java.lang.String, Double) = (23,foo,18.0)
scala> t.getClass
res31: java.lang.Class[_] = class scala.Tuple3
scala> val t1 = ('a', "quick", 23, "year-old", """foxy""".r, List(1, 2, 3))
t1: (Char, java.lang.String, Int, java.lang.String, scala.util.matching.Regex, List[Int]) = (a,quick,23,year-old,foxy,List(1, 2, 3))
scala> t1.getClass
res32: java.lang.Class[_] = class scala.Tuple6
I’m not going to provide an example of creating a Tuple22; that is left as an exercise. 🙂 I would argue that if you need more than three elements, you really should define a class to hold them. I think that beyond three elements it gets difficult to keep them straight. Tuples are great for holding two or three pieces of information, but don’t go crazy with them.