Closure syntax
In the previous lesson every function for applyTwice was declared separately: with func and a name. But when a function is needed once, giving it a name is awkward. It is like copying a sandwich recipe into a cookbook only to hand it to a friend once. It is handier to write the recipe right on the card you hand over.
A function without a name, written right where it is needed, is called a closure:
func applyTwice(_ f: (Int) -> Int, to x: Int) -> Int {
f(f(x))
}
let triple = { (x: Int) -> Int in
x * 3
}
print(triple(2)) // 6
print(applyTwice({ (x: Int) -> Int in x + 10 }, to: 1)) // 21Let's go through the program:
applyTwicecalls the function it receives twice.- The constant
tripleholds a closure that triples a number. You call it the same way as a function:triple(2)gives6. - In the last line the closure is written right in the call to
applyTwice, in place of the first argument. It adds10, so1turns into11and then into21.
What a closure is made of
{ (x: Int) -> Int in x * 3 } is a function without the word func and without a name. Its parts:
- the curly braces — the start and the end of the closure;
(x: Int) -> Int— the parameters and the result type, as in a function's header;in— the word that separates the header from the body;x * 3— the body; if it is one expression, noreturnis needed, as with a function.
The type of triple is (Int) -> Int. A closure is the same kind of value as a function: you put it into a constant, pass it as an argument and return it from a function.
Short forms
The full form is long, and Swift lets you shorten it. Shortening works best where the compiler already knows which function type is needed — for example, in an argument of applyTwice or sorted(by:):
print(applyTwice({ x in x + 10 }, to: 1)) // 21
print(applyTwice({ $0 + 10 }, to: 1)) // 21
let numbers = [5, 2, 8, 1]
print(numbers.sorted(by: { a, b in a > b })) // [8, 5, 2, 1]
print(numbers.sorted { $0 > $1 }) // [8, 5, 2, 1]
print(numbers.sorted(by: >)) // [8, 5, 2, 1]Without types
The parameter f of applyTwice has the type (Int) -> Int. So the closure can leave the types out: { x in x + 10 }. The compiler works out that x is an Int and that the result is an Int too. It is the same type inference as in let n = 5.
The names $0 and $1
You can skip naming the parameters altogether. Then the first parameter is called $0, the second $1 and so on, and the header disappears together with in: { $0 + 10 }. Short one-line closures are written this way. In a long closure, parameters with their own names are easier to read.
A closure after the parentheses
The method sorted(by:) takes a function of type (Int, Int) -> Bool. It receives two elements and answers whether the first should come before the second. Let's go through the two calls:
{ a, b in a > b }putsabeforebifais greater. The order is descending.sorted { $0 > $1 }is the same closure moved outside the call's parentheses. If a closure is the last argument, you can write it after the closing parenthesis. There are no other arguments here, so both the parentheses and the labelby:are gone.
An operator instead of a closure
sorted(by: >) is the shortest form. The operator > for numbers is also a function of type (Int, Int) -> Bool. So you pass it by name, like any function.
Why "closure"
Here the recipe-card picture is not exact. A paper card knows nothing about the kitchen where it was written. A closure sees the constants and variables around the place where it was written:
let bonus = 5
print(applyTwice({ $0 + bonus }, to: 1)) // 11bonus is not a parameter of the closure but a constant next to it. The closure uses it the same way as its $0: 1 + 5 gives 6, and 6 + 5 gives 11. That is where the name comes from: a closure closes over the names of the code around it.
Interviewers like to ask this: what is a closure? The answer has three parts:
- a function without a name, written right in the code, in curly braces;
- it can be stored in a constant and passed as an argument;
- inside, it sees the constants and variables around it.
Common mistake
The closure grew to two lines, and return was not written:
let shout = { (word: String) -> String in
let loud = word.uppercased()
loud + "!" // ✗
}error: missing return in closure expected to return 'String'The program does not compile. Only a body of one expression can do without return. Here the body has two lines, and Swift treats the last one as an ordinary statement, not as the result. That is why, above the error, the compiler also warns that the result of + is unused. Add return: return loud + "!".
Key points
- A closure is a function without a name in curly braces:
{ (x: Int) -> Int in x * 3 }; the wordinseparates the header from the body. - Where the function type is known, you can leave the types out and call the parameters
$0,$1. - A closure that is the last argument can move outside the call's parentheses:
numbers.sorted { $0 > $1 }. - A closure sees the constants and variables around the place where it was written.