Baeldung Pro – Scala – NPI EA (cat = Baeldung on Scala)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

1. Overview

In this tutorial, we’ll explore how to convert a Scala List to a Tuple. This could be beneficial if we’re using an existing function that requires a Tuple. Or to make data easier to reason about in our code by having a fixed number of elements in a Tuple. It’s worth noting that we could solve this problem differently if our project uses Shapeless. However, in this tutorial, we’ll focus on solutions using the standard Scala library.

We’ll first cover the unapply() function and the ways it can help us solve this problem. We’ll then take a look a several approaches to solving this problem, making sure to note any potential downsides to each solution.

2. Scala Extractors and unapply()

unapply() can be overridden by classes in Scala and is present on many of the classes in the Scala standard library, including List. The unapply() function allows us to use extractors, splitting out the different arguments used to create a class. This, is then what powers pattern matching within Scala. In the context of Lists, unapply() extracts each element in the List.

3. List of a Fixed-Size

If we know the size of the List we need to convert, we can directly extract the elements into a variable:

val first :: second :: _ = List("Hello", "world")
(first, second) // = ("Hello", "world")

The code above produces a Tuple2, containing each of the elements in the List. This approach can be modified to handle any List size we need.

After the second variable, we assign the remaining elements to the wildcard operator. So, this code will ignore anything after the second element. However, the List must contain at least the number of elements required. Otherwise, this code will throw a MatchError exception.

4. List of an Unknown Size

To handle a List of an unknown size, we need to use pattern matching to allow us to handle all cases for the number of elements in the List. How we do this depends on whether the resulting Tuple should be a fixed size or not.

4.1. Producing a Tuple of a Fixed Size

Using pattern matching we can take a similar approach to the extractor we used in the previous section. However, since we’re using pattern matching, we also provide a default case for when the List has fewer elements than required:

List("Hello", "world") match {
  case first :: second :: _ => (first, second)
  case _ => ("", "")
}

Let’s write some test cases for this function as well:

it should "convert list with 2 elements" in {
  ConvertListToTuple.twoElementsToTupleUsingMatch(
    List("Hello", "world")
  ) shouldBe ("Hello", "world")
}

it should "convert list with 3 elements to tuple2 ignoring extra elements" in {
  ConvertListToTuple.twoElementsToTupleUsingMatch(
    List("Hello", "world", "!")
  ) shouldBe ("Hello", "world")
}

"twoElementsToTupleUsingMatch" should "return empty Strings for 1 element" in {
  ConvertListToTuple.twoElementsToTupleUsingMatch(List("Hello")) shouldBe ("", "")
}

This code successfully converts a List of two or more elements to a Tuple2, as our first solution did. However, in the default case, we’re also handling the situation where the List contains one, or no elements and returning a Tuple2 of empty Strings. This can be adjusted to supply any default that’s suitable for our use case. In which case we’ll have to deal with the default value appropriately in any code that calls this function.

4.2. Producing a Tuple of a Dynamic Size

To convert a List of an unknown size into a Tuple of the same size, we need to handle all required List sizes in our match:

val result: Tuple = List("Hello", "world") match {
  case first :: second :: third :: _ => (first, second, third)
  case first :: second :: _ => (first, second)
  case first :: _ => Tuple1(first)
  case _ => ("", "")
}

This pattern match handles List sizes of two or three and will convert them into a Tuple2 or Tuple3 respectively. We can add additional cases to handle and convert as many elements as required.

Let’s write out some unit tests to ensure this pattern match works as expected:

it should "return empty Strings for Nil" in {
  ConvertListToTuple.unknownSizeToTuple(Nil) shouldBe ("", "")
}

"unknownSizeToTuple" should "convert list of 1 element to tuple1" in {
  ConvertListToTuple.unknownSizeToTuple(List("Hello")) shouldBe Tuple1("Hello")
}

"unknownSizeToTuple" should "convert list of 2 elements to tuple2" in {
  ConvertListToTuple.unknownSizeToTuple(List("Hello", "world")) shouldBe ("Hello", "world")
}

it should "convert list of 3 elements to tuple3" in {
  ConvertListToTuple.unknownSizeToTuple(
    List("Hello", "world", "!")
  ) shouldBe ("Hello", "world", "!")
}

The issue in converting to a Tuple of dynamic size is that it’s not typesafe. As we don’t know whether the code above will return a Tuple2 or Tuple3. Therefore, the resulting type has to be the Tuple trait. This then adds complexity when trying to get the values out of the Tuple, as we’d have to match again to handle each case for what the Tuple length could be.

5. Argument Limits on Tuples

In each of these solutions, it’s worth keeping in mind that in Scala 2.13 and below Tuples are limited to 22 elements.  Meaning, that when using Scala 2.13 or below only a maximum of 22 elements in List can be converted to a Tuple. Fortunately, Scala 3 solves this issue with the introduction of the TupleXXL, which allows tuples of any size.

6. Conclusion

In this article, we have covered three different approaches to converting a List to a Tuple. We started by extracting the elements directly to a variable and discussed the potential of a MatchError exception when doing this on a List not containing enough elements. Next, we used pattern matching and provided cases to handle Lists of any size. Finally, we added cases to the pattern match to return Tuples of a dynamic size. However, we’re aware of the type safety issue in doing this. We also mentioned that there is a  22-element limit on Tuples, in Scala 2.13 and below.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.