Reading — step 1 of 7
Learn
Tuples are anonymous records. Combined with destructuring, they're a clean way to return multiple values without defining a case class — and they pair well with pattern matching.
Tuple basics
val point = (3, 4) // Tuple2[Int, Int]
val triple = (1, "hello", true) // Tuple3[Int, String, Boolean]
println(point._1) // 3
println(point._2) // 4
println(triple._3) // true
Tuples are types Tuple1 through Tuple22. The (a, b, c) syntax is shorthand. Access fields with ._1, ._2, etc. (1-based).
Destructuring assignment
val (x, y) = (3, 4)
println(s"x=$x, y=$y") // x=3, y=4
val (a, b, c) = computeThree()
Much nicer than tuple._1, tuple._2 everywhere.
Tuples as function returns
Returning multiple values without defining a class:
def divmod(a: Int, b: Int): (Int, Int) = (a / b, a % b)
val (q, r) = divmod(17, 5)
println(s"$q remainder $r") // 3 remainder 2
For return values that don't have a named class, tuples are concise. For things that are reused or need a domain name (Coordinate, UserId), prefer a case class.
Tuples in pattern matching
def describe(point: (Int, Int)): String = point match {
case (0, 0) => "origin"
case (0, _) => "y-axis"
case (_, 0) => "x-axis"
case (x, y) if x == y => "diagonal"
case (x, y) => s"point at ($x, $y)"
}
Tuples destructure naturally inside case patterns. Powerful for state machines and predicate-based dispatch.
Map iteration uses tuples
val ages = Map("Ada" -> 36, "Bob" -> 25)
for ((name, age) <- ages) {
println(s"$name is $age")
}
ages.foreach { case (name, age) =>
println(s"$name: $age")
}
Map[K, V] iterates as (K, V) tuples. Destructure in the loop or via case.
zip — pairing collections
val names = List("Ada", "Bob", "Carol")
val ages = List(36, 25, 40)
val paired = names.zip(ages)
// List((Ada, 36), (Bob, 25), (Carol, 40))
paired.map { case (n, a) => s"$n is $a" }
zipWithIndex is a special case:
List("a", "b", "c").zipWithIndex
// List((a, 0), (b, 1), (c, 2))
Useful when you want both element and position in map/filter.
Tuple vs case class
Tuples are faster to write but lose meaning:
// Tuple — anonymous
def parsePoint(s: String): (Int, Int) = ???
// Case class — named
case class Point(x: Int, y: Int)
def parsePoint2(s: String): Point = ???
val p = parsePoint("3,4")
p._1 // What does _1 mean?
val p2 = parsePoint2("3,4")
p2.x // Clear: x coordinate
Rule of thumb:
- Tuple — short-lived, local, 2-3 values
- Case class — anything that has a name, is returned across module boundaries, or has more than 3 fields
Common mistakes
- Tuples beyond 3-4 elements — readability collapses.
tuple._5is meaningless. Use a case class. - Tuples in public APIs — caller has to remember positional meaning. Document it or (better) use a named type.
- Confusing tuple equality with case class —
(1, 2) == (1, 2)is true (structural). Plain class without case is reference equality. - Mixing tuple syntax with pattern syntax in for-loops —
for (k, v <- map)doesn't work; usefor ((k, v) <- map)orcase (k, v) =>.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…