1. Overview

Sometimes we need a way to group several elements together that may or may not be related. For this very scenario, we can use a tuple, which simply put is an ordered collection of objects of different types.

In a previous tutorial, we took a look at a neat third-party library called javatuples which offers support for tuples in Java. Thankfully, Scala already has a built-in tuple type, which is an immutable data structure.

In this quick tutorial, we’ll learn how to create a tuple in Scala, access its elements, and review some of the most important methods and discuss when to use them.

2. Creating Tuples

Creating a tuple is very easy and doesn’t require any boilerplate code. It’s just a matter of enclosing its elements in parentheses:

val tuple: (String, Int) = ("Joe", 34)

Here we’ve created a tuple containing a String element and an Int element. It’s also possible to create them using arrow syntax:

val stillTuple: (String, Int) = "Joe" -> 34

In Scala 2, tuples are defined using a series of classes named Tuple2, Tuple3 all the way through to Tuple22. In practice, we’ll probably never need to use these classes directly, but it’s interesting to know what’s happening internally. Since Scala 2 only has implementation till Tuple22, we can create tuples of maximum 22 elements.

However, in Scala 3, the 22-limit for tuples is removed by introducing TupleXXL and now we can have tuples with more than 22 elements.

Let’s take a quick look at how we can create a tuple with a few more elements:

val tuple3: (String, Int, Boolean) = ("Joe", 34, true)
val tuple4: (String, Int, Boolean, Char) = ("Joe", 34, true, 'A')

We should bear in mind that (String, Int, Boolean)* is just syntactic sugar for *Tuple3[String, Int, Boolean].

In the next section, we’ll take a look at how we can access elements in a tuple.

3. Accessing Elements

We can access the elements inside a tuple in one of two ways. The first approach is to access elements by position using the underscore syntax.

For example, we can use the _1 method to access the first element,  _2 to access the second and so on (up to 22):

val name = tuple._1 
val age = tuple._2

In Scala 3, we can also access the elements by their index, just like accessing a List or an Array:

val name = tuple(0)

We can also assign a tuple to multiple variables variable using pattern matching:

val (userName, userAge) = tuple

Here, we take the tuple apart and assign the first element to a variable called userName whose inferred type is String. Likewise, we assign the second element to a variable called userAge with an inferred type of Int.

It’s also possible to omit some elements when we declare the variables using an underscore in the desired place:

val (_, myAge) = tuple

4. Typical Use Cases

Tuples can be particularly useful when we need to return multiple values from a function or pass multiple values to a function. This may be preferable instead of using a case class, particularly if we need to return unrelated values.

4.1. Returning Multiple Values

A particularly good example of returning a tuple is a partition() method:

def partition[A](xs: List[A])(predicate: A => Boolean): (List[A], List[A]) = {
  xs.foldRight((List.empty[A], List.empty[A])) {
    case (a, (lefts, rights)) =>
      if (predicate(a)) (a :: lefts, rights) else (lefts, a :: rights)
  }
}
val (evens, odds) = partition(List(1, 3, 4, 5, 2))(_ % 2 == 0)

In this example, the partition() method returns a pair of List. The first list consists of all the elements which satisfy a given predicate and the second consists of all the elements that don’t.

4.2. Passing Multiple Values

Likewise*,* we can also use a tuple for passing multiple parameters to a one-parameter function. We even have a special tupled() method that converts a function with more than one parameter, to a function which accepts a tuple as the only argument:

val data = Map(
    "Joe" -> 34,
    "Mike" -> 16,
    "Kelly" -> 21
  )

case class User(name: String, isAdult: Boolean)

val createUser: (String, Int) => User = (name, age) => User(name, age >= 18)
val users = data.map(createUser.tupled)

In the above example, we transform our data map to User class using createUser function. If we forget to call the tupled() method, it would fail with the following error:

type mismatch; 
found : (String, Int) => User 
required: ((String, Int)) => ? 
val users = data.map(createUser)

The compiler is telling us that we try to pass two arguments function into a place where one argument function with tuple parameter is needed.

5. Tuples in Scala 3

Scala 3 brought a lot of improvement to tuples. In Scala 2, the functionalities of a tuple were very limited. However, in Scala 3, we can use tuples more or less like an indexed collection.

Let’s look at some of the newly introduced methods on tuples:

val tuple = (1, "Baeldung", false)
assert(tuple(0) == 1)
assert(tuple.head == 1)
assert(tuple.tail == ("Baeldung", false))
assert(tuple.last == false)
assert(tuple.take(2) == (1, "Baeldung"))
assert(tuple.toList == List(1, "Baeldung", false)
assert(tuple ++ ("Hello", "World") == (1, "Baeldung", false, "Hello", "World"))

Scala 3 uses the newly introduced Match Type feature under the hood to implement the tuple functionalities.

We can also generate the corresponding tuple from any case class easily using the fromProductTyped() method:

final case class User(id: Long, name:String, active:Boolean)
val baeldung = User(1, "baeldung", true)
val userTuple = Tuple.fromProductTyped(baeldung)
assert(userTuple == (1, "baeldung", true))

6. Conclusion

In this quick tutorial, we learned about the tuple type in Scala. We saw how to create tuples and how to access values inside of tuple objects. We also reviewed several useful methods and walked through some typical use cases. Additionally, we also looked at the improvements in Scala 3 for tuples.

As always, the full source code of the article showing Tuple in Scala 2 is available over on GitHub. Additionally, the source code for Scala 3 Tuple is also available over on GitHub.


« 上一篇: Scala 中的高阶函数
» 下一篇: Scala 排序指南