What the Heck Is a Tuple, Anyway?

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.

Advertisements

2 thoughts on “What the Heck Is a Tuple, Anyway?

  1. Nice example! The distinction between Tuples and other data structures in Scala makes more sense to me now. It almost seems to me like an anonymous, immutable struct.

    Based on your descriptions, I think I would prefer a Tuple in the case where I wanted to return an small, immutable set of values of different types to a caller. It is concise, clear, type-safe, and it seems very easy to re-factor to a proper class if you need to add functionality.

    It also seems like it can be easily abused by putting way too many values in it, or by passing that Tuple around to multiple classes. I would say that if you need more than 2-3 values, or you pass the Tuple to more that 2-3 different objects, then you need to use a class. It’s _too_ easy to create value classes in Scala not to.

Comments are closed.