Tuples
In this chapter, you’ll learn how to group multiple values into a single compound value known as a tuple.
You create a tuple by listing its elements, separated by commas and enclosed in parentheses. For example:
var point = (4.3, -13.7)
This tuple represents a two-dimensional point. Its type is (Double, Double)
.
A tuple is heterogeneous, meaning its elements can be of different types:
var response = (404, "Not Found")
The type of this tuple is (Int, String)
.
You can use any type in a tuple, even another tuple type.
Elements
A tuple assigns its elements an index, starting at index 0 for the first element. You use this index, preceded by a dot, to access the elements:
print(response.0)
point.1 = 2.7
Of course, you can’t mutate a tuple’s elements if the tuple is a constant:
let origin = (0.0, 0.0)
origin.0 = -1.0 // Invalid!
Element names
You can name a tuple’s elements to clarify their meaning:
var secondPoint = (x: 5.7, y: 1.8)
This lets you access the elements by name:
secondPoint.x = 2
Element names are part of a tuple’s type. The type of secondPoint
is (x: Double, y: Double)
, not just (Double, Double)
. Fortunately, these types are compatible.
The following assignment is allowed:
secondPoint = point
This assigns point.0
to secondPoint.x
and point.1
to secondPoint.y
.
The reverse is also allowed:
point = secondPoint
This assigns secondPoint.x
to point.0
and secondPoint.y
to point.1
.
However, tuples with different element names aren’t compatible because Swift assumes they have a different meaning:
var measurement = (width: 1.2, height: 5.6)
secondPoint = measurement
The next section describes a technique you can use to perform this assignment differently.
Decomposition
You can decompose a tuple into its elements:
var (code, message) = response
This declares two variables, code
and message
, and assigns response.0
to code
and response.1
to message
.
If you want to assign only some of a tuple’s elements, you can use the wildcard pattern (_
) to skip the elements you don’t want:
(_, message) = response
This assigns only response.1
to message
.
You can use decomposition to assign tuples with conflicting element names:
(secondPoint.x, secondPoint.y) = measurement
This assigns measurement.0
to secondPoint.x
and measurement.1
to secondPoint.y
. The element names of measurement
don’t matter here because you’re decomposing the tuple and assigning its elements separately.
You can achieve the same result as follows:
secondPoint = (measurement.width, measurement.height)
This creates a new tuple containing the elements of measurement
and assigns this tuple to secondPoint
. The new tuple doesn’t have element names, so its type is compatible with secondPoint
.
Comparison operators
You can use the familiar comparison operators to compare tuples. Set point
to its initial value of (4.3, -13.7), then try the following:
point == (4.3, -13.7)
point < (6.4, -15.6)
point > (4.3, -15.6)
These operators perform their comparisons from left to right:
- In the first expression, the operator first compares
point.0
and 4.3, thenpoint.1
and -13.7. Both pairs are equal, so the operator returnstrue
. - In the second expression, the operator returns
true
becausepoint.0
is less than 6.4, regardless of the remaining elements. - In the third expression, the operator first compares
point.0
and 4.3. These are equal, so the operator moves to the second pair of elements.point.1
is greater than -15.6, so the operator returnstrue
.
The comparison operators are less strict than the assignment operator in that they ignore element names. So you can, for example, check if secondPoint
and measurement
contain the same values:
secondPoint == measurement
Switching over tuples
A switch
statement can test all of the elements in a tuple at once. For example:
switch point {
case (0, 0):
print("This point is the origin.")
case (_, 0):
print("This point is on the x-axis.")
case (0, _):
print("This point is on the y-axis.")
default:
break
}
This example uses the tuple pattern. As its name implies, this pattern consists of a tuple of patterns. For example, the tuple pattern (0, _)
consists of an expression pattern (0
) and the wildcard pattern (_
). In the example, the switch
statement decomposes point
and matches each element against the corresponding pattern.
The next example uses the tuple pattern to implement the currentPlayerHasWon
function from Tic-Tac-Toe:
func currentPlayerHasWon() -> Bool {
switch (currentPlayer, currentPlayer, currentPlayer) {
case (topLeft, top, topRight),
(left, center, right),
(bottomLeft, bottom, bottomRight),
(topLeft, left, bottomLeft),
(top, center, bottom),
(topRight, right, bottomRight),
(topLeft, center, bottomRight),
(topRight, center, bottomLeft):
true
default:
false
}
}
Note
This function has an implicit return statement that returns the result of a switch
expression.
This code checks if any of the rows, columns, or diagonals are filled with the current player’s symbol. The first case
contains a list of tuple patterns that group the values in each row, column, or diagonal. The switch
statement matches these patterns against a tuple that contains three copies of the player’s symbol.
The tuple pattern is often combined with the value-binding pattern. A value binding uses let
or var
to assign (bind) a local name to a matched value. For example, the binding let x
matches any value and assigns it the name x
. Cases that use value binding often include a where
clause to restrict their matches.
Here’s an example:
switch point {
case (0, 0):
print("This point is the origin.")
case (let x, 0):
print("This point is on the x-axis at x=\(x).")
case (0, let y):
print("This point is on the y-axis at y=\(y).")
case let (x, y) where x > 0 && y > 0:
print("This point is in the first quadrant at x=\(x) and y=\(y).")
case let (x, y) where x < 0 && y > 0:
print("This point is in the second quadrant at x=\(x) and y=\(y).")
case let (x, y) where x < 0 && y < 0:
print("This point is in the third quadrant at x=\(x) and y=\(y).")
case let (x, y) where x > 0 && y < 0:
print("This point is in the fourth quadrant at x=\(x) and y=\(y).")
default:
break
}
This example shows how you can combine the value-binding pattern with the tuple pattern:
(let x, 0)
and(0, let y)
are tuple patterns that have a value-binding pattern as one of their elements.let (x, y)
is a value-binding pattern that uses a tuple pattern to bind two values. In this case, the keywordlet
applies to bothx
andy
.
Tuples as return values
You can use a tuple to return multiple values from a function. Here’s a function that returns the width and height of a rectangle formed by two given points:
func sizeOfRectangle(
from corner1: (x: Double, y: Double),
to corner2: (x: Double, y: Double)
) -> (width: Double, height: Double) {
let width = abs(corner2.x - corner1.x)
let height = abs(corner2.y - corner1.y)
return (width, height)
}
Note
This example uses a different formatting style to improve the readability of the lengthy function declaration.
This function uses tuples for both its parameters and its return type. You call it as follows:
let size = sizeOfRectangle(from: (-2, 2), to: (1, -1))
The inferred type of size
is (width: Double, height: Double)
, so you can access its elements by name:
size.width
size.height
Type aliases
If you often use the same tuple type, you can declare a type alias for it:
typealias Point = (x: Double, y: Double)
typealias Size = (width: Double, height: Double)
This declares Point
as an alias for the type (x: Double, y: Double)
, and Size
as an alias for (width: Double, height: Double)
.
typealias
creates shorthands for complex types and introduces type names that are specific to your application. A type alias doesn’t add any functionality, but it can make your code clearer and less verbose:
func sizeOfRectangle(from corner1: Point, to corner2: Point) -> Size {
let width = abs(corner2.x - corner1.x)
let height = abs(corner2.y - corner1.y)
return (width, height)
}
In Part IV, you’ll learn how you can declare your own custom types that are more than aliases for existing types.
Up next
In this chapter, you learned how you can use tuples to group values. Tuples have many uses; however, their number of elements is fixed when you declare them. This means you can’t use a tuple to, for example, keep track of the movies you want to watch.
In Arrays and Dictionaries, you’ll learn about types that represent a collection of elements. These collections can grow to accommodate any number of elements you want to store.
Before you can learn about collections, you first need to learn about optionals. This is the topic of the next chapter.