Swift Functions and Closures: map, filter and reduce

iOS App Development with Swift and SwiftUI: Beginner to Job-Ready Projects · Swift Basics · 5 min read · Updated

map, filter, reduce

  1. You start with an array of values.
  2. filter keeps only the values that pass your test.
  3. map changes each remaining value into a new one.
  4. reduce folds the list into one value, such as a total.

A function is a named piece of code that you can run whenever you need it. A closure is a function without a name that you can pass around like a value. SwiftUI uses both on nearly every line, so this lesson is time well spent. We keep the Expense Tracker idea: amounts of money and what we do with them.

Writing a function

A function starts with func, then a name, then its inputs in brackets, then an arrow and the type it gives back. Swift functions have a special habit: each input has an argument label that you write when you call the function. This makes calls read like short sentences.

func total(of amounts: [Double]) -> Double {
    var sum = 0.0
    for a in amounts { sum += a }
    return sum
}

let t = total(of: [12.5, 40, 7.25])
print(t)   // 59.75

Look at total(of amounts:). The first word, of, is the label used when you call it. The second word, amounts, is the name used inside the function. If you do not want a label, write an underscore: func total(_ amounts: [Double]). Then you call total([1, 2]). If a function gives nothing back, you leave out the arrow.

Default values

An input can have a default. Callers may then leave it out. This keeps simple calls short and still allows detail when you need it.

func format(_ amount: Double, currency: String = "$") -> String {
    return "\(currency)\(String(format: "%.2f", amount))"
}

print(format(5))                  // $5.00
print(format(5, currency: "EUR ")) // EUR 5.00

Closures

A closure is a block of code in curly braces. Its inputs come first, followed by the word in. You can store it in a constant, pass it to a function, or return it.

let double = { (x: Double) -> Double in
    return x * 2
}
print(double(4))   // 8.0

func repeatTask(times: Int, action: () -> Void) {
    for _ in 0..<times { action() }
}

repeatTask(times: 3) {
    print("Saved a backup")
}

Look at the last call. When the last input of a function is a closure, you can write the closure *after* the brackets. This is called a trailing closure. You will see this shape in SwiftUI all the time, for example in Button("Add") { ... }, where the braces hold the code that runs when someone taps.

map, filter, sorted and reduce

Arrays have helpers that take a closure. They replace many small loops, and they read as a description of what you want.

let amounts = [12.5, 40, 7.25, 99, 3]

let doubled = amounts.map { $0 * 2 }
let large = amounts.filter { $0 > 10 }
let sorted = amounts.sorted { $0 > $1 }
let sum = amounts.reduce(0) { $0 + $1 }

print(doubled)  // [25.0, 80.0, 14.5, 198.0, 6.0]
print(large)    // [12.5, 40.0, 99.0]
print(sorted)   // [99.0, 40.0, 12.5, 7.25, 3.0]
print(sum)      // 161.75
  • map makes a new array by changing each item.
  • filter keeps only the items for which the closure says true.
  • sorted puts the items in order. The closure says which of two items comes first.
  • reduce folds all items into one value. The first input is the starting value, here 0.
  • $0 and $1 are short names for the first and second inputs. They are fine for tiny closures. When a closure grows, give the inputs real names, because the code is easier to read later.

Functions inside types

When a function lives inside a struct or class, it is called a method. In the next lesson you will put total inside a type that holds expenses. Closures also *capture* the values they use from the code around them. This is powerful, and also the reason that, in classes, you sometimes meet the words [weak self] in longer examples. We come back to this when it matters.

Common mistakes

  • Forgetting the argument label when you call a function. Swift's error message names the missing label.
  • Writing a very long closure on one line. Split it onto several lines with named inputs.
  • Using reduce when a plain loop would be clearer. Choose the one a teammate would understand fastest.

Your task

  1. Write average(of:) that takes an array of Double and returns the average. Return 0 for an empty array.
  2. Write isLarge(_:limit:) with a default limit of 50 that returns a Bool.
  3. Given an array of amounts, use filter to keep those above 20, then map to add 10 per cent, then reduce to total them.
  4. Call your own function with a trailing closure, like repeatTask above.

How to check it worked

  • average(of: [10, 20, 30]) prints 20.0 and average(of: []) prints 0.0.
  • You can explain what $0 means.
  • You can say what a trailing closure is and find one in the examples.

Frequently asked questions

What is the difference between a function and a closure?

A function has a name. A closure is a block of code without a name that you can store in a constant or pass to another function. Functions are really a special kind of closure.

What does $0 mean in Swift?

It is a short name for the first input of a closure. $1 is the second. Use them for tiny closures and write real names when the closure grows.

What is a trailing closure?

When the last input of a function is a closure, you can write it in braces after the brackets. SwiftUI buttons and lists use this style all the time.