Tutorial outline

Unit 1

  1. 01Getting started
  2. 02Names and functions
  3. 03Control
  4. 04Higher-order functions
  5. 05Environments and closures
  6. 06Functional abstraction
  7. 07Recursion
  8. 08Tree recursion

Unit 2

  1. 09Sequences
  2. 10Dictionaries, sets, and tuples
  3. 11Optionals
  4. 12Structures and enumerations
  5. 13Linked lists and trees
  6. 14Mutation and value semantics
  7. 15Classes and inheritance
  8. 16Protocols and generics
  9. 17Iterators and lazy sequences
  10. 18Efficiency and memoization

Unit 3

  1. 19Algebraic data types and pattern matching
  2. 20Writing an interpreter
  3. 21Error handling
  4. 22Concurrency with async and await
  5. 23Testing
  6. 24Building a Swift package

Higher-order functions

Function types, functions as arguments and return values, closure expressions, and the map, filter, and reduce that Swift builds in.

CS61A
Higher-Order Functions · Composing Programs 1.6
Swift book
Functions · Closures

Here are three functions that look almost the same:

func sumNaturals(_ n: Int) -> Int {
    var total = 0
    for k in 1...n { total += k }
    return total
}
func sumCubes(_ n: Int) -> Int {
    var total = 0
    for k in 1...n { total += k * k * k }
    return total
}
func sumSquares(_ n: Int) -> Int {
    var total = 0
    for k in 1...n { total += k * k }
    return total
}
print(sumNaturals(5), sumCubes(5), sumSquares(5))   // 15 225 55

They differ only in what is added for each k. CS61A’s answer is to make that difference a parameter. The parameter is not a number but a function. A function that takes or returns a function is a higher-order function.

Function types

Every function has a type written from its parameters and result. square below has type (Int) -> Int: it takes one Int and returns an Int. Functions are values, so they can be bound to names like any other value:

func square(_ x: Int) -> Int { x * x }

let f: (Int) -> Int = square
print(f(7))   // 49

Note there are no parentheses after square on the second line. square is the function itself; square(7) would call it.

Functions as arguments

Now the three sums collapse into one:

func summation(_ n: Int, _ term: (Int) -> Int) -> Int {
    var total = 0
    for k in 1...n {
        total += term(k)
    }
    return total
}

func cube(_ x: Int) -> Int { x * x * x }

print(summation(5, cube))   // 225

summation knows how to add things up; cube knows what to add. Neither has to know about the other’s job.

Closure expressions

Naming a tiny function just to pass it once is clumsy. A closure expression writes a function inline. The full form spells out the types, then in, then the body:

func summation(_ n: Int, _ term: (Int) -> Int) -> Int {
    var total = 0
    for k in 1...n { total += term(k) }
    return total
}

print(summation(5, { (k: Int) -> Int in return k * k }))
print(summation(5, { k in k * k }))      // types inferred
print(summation(5, { $0 * $0 }))         // shorthand argument names
print(summation(5) { $0 * $0 })          // trailing closure

All four lines print 55. Swift can infer the closure’s types from summation’s parameter, a single-expression body returns its value, and $0 names the first argument. When the closure is the last argument, it can be written after the parentheses as a trailing closure.

What would Swift print?

let twice = { (x: Int) in x * 2 }
print(twice(twice(3)))

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

Answer12

twice is bound to a closure. twice(3) is 6, and twice(6) is 12.

Functions as return values

A function can build and return a new function:

func makeAdder(_ n: Int) -> (Int) -> Int {
    return { x in x + n }
}

let addThree = makeAdder(3)
print(addThree(4))        // 7
print(makeAdder(10)(1))   // 11

The return type (Int) -> Int says makeAdder gives back a function. The closure it returns uses n, a parameter of makeAdder, even after makeAdder has returned. How that works is next lesson’s topic.

What would Swift print?

func makeAdder(_ n: Int) -> (Int) -> Int {
    return { x in x + n }
}
let addTwo = makeAdder(2)
print(addTwo(addTwo(1)), makeAdder(5)(0))

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

Answer5 5

addTwo(1) is 3 and addTwo(3) is 5. makeAdder(5) returns a function that adds 5, and calling it on 0 gives 5.

A closure that is stored or returned, and so may run after the function receiving it has returned, must be marked @escaping. Swift requires the mark so that you notice when a function holds onto one.

Put the lines in order

Put the lines in order. compose(f, g) should return a function that applies g and then f; the program prints 12.

  1. print(incrementThenDouble(5))
  2. return { x in
  3. f(g(x))
  4. func compose(_ f: @escaping (Int) -> Int, _ g: @escaping (Int) -> Int) -> (Int) -> Int {
  5. }
  6. let incrementThenDouble = compose({ $0 * 2 }, { $0 + 1 })
  7. }

Show the correct program
func compose(_ f: @escaping (Int) -> Int, _ g: @escaping (Int) -> Int) -> (Int) -> Int {
    return { x in
        f(g(x))
    }
}
let incrementThenDouble = compose({ $0 * 2 }, { $0 + 1 })
print(incrementThenDouble(5))

map, filter, and reduce

Swift’s collections come with higher-order functions built in. map applies a function to every element, filter keeps the elements for which a function returns true, and reduce combines everything into one value:

let numbers = [1, 2, 3, 4, 5]
print(numbers.map { $0 * $0 })          // [1, 4, 9, 16, 25]
print(numbers.filter { $0 % 2 == 1 })   // [1, 3, 5]
print(numbers.reduce(0, +))             // 15

reduce(0, +) starts from 0 and combines with +. Operators are functions, so + can be passed wherever a (Int, Int) -> Int is expected.

What would Swift print?

print([1, 2, 3, 4].filter { $0 % 2 == 0 }.map { $0 * 10 })
print([1, 2, 3].reduce(1, *))

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

Answer[20, 40]\n6

filter keeps [2, 4], then map multiplies each by 10. reduce(1, *) multiplies 1 × 1 × 2 × 3 = 6.

Lab 3: Higher-order functions

Q1: Product

Write product(n, term), which returns term(1) * term(2) * … * term(n).

func product(_ n: Int, _ term: (Int) -> Int) -> Int {
    // your code here
}
Show solution
func product(_ n: Int, _ term: (Int) -> Int) -> Int {
    var total = 1
    for k in 1...n {
        total *= term(k)
    }
    return total
}

assert(product(3, { $0 }) == 6)          // 1 * 2 * 3
assert(product(5, { $0 }) == 120)        // 5 factorial
assert(product(3, { $0 * $0 }) == 36)    // 1 * 4 * 9
assert(product(1, { $0 * 7 }) == 7)

Q2: Accumulate

summation and product share a pattern too. Write accumulate(merger, start, n, term), which merges start with term(1), …, term(n) using the two-argument function merger. Then define summation and product in one line each using accumulate.

func accumulate(_ merger: (Int, Int) -> Int, _ start: Int, _ n: Int, _ term: (Int) -> Int) -> Int {
    // your code here
}
Show solution
func accumulate(_ merger: (Int, Int) -> Int, _ start: Int, _ n: Int, _ term: (Int) -> Int) -> Int {
    var total = start
    for k in 1...n {
        total = merger(total, term(k))
    }
    return total
}

func summation(_ n: Int, _ term: (Int) -> Int) -> Int { accumulate(+, 0, n, term) }
func product(_ n: Int, _ term: (Int) -> Int) -> Int { accumulate(*, 1, n, term) }

assert(accumulate(+, 0, 5, { $0 }) == 15)
assert(accumulate(+, 11, 5, { $0 }) == 26)
assert(accumulate(*, 2, 3, { $0 * $0 }) == 72)   // 2 * 1 * 4 * 9
assert(summation(5, { $0 * $0 * $0 }) == 225)
assert(product(5, { $0 }) == 120)

Q3: Make a repeater

Write makeRepeater(f, n), which returns a function that applies f to its argument n times. When n is 0, the returned function should return its argument unchanged.

func makeRepeater(_ f: @escaping (Int) -> Int, _ n: Int) -> (Int) -> Int {
    // your code here
}
Show solution
func makeRepeater(_ f: @escaping (Int) -> Int, _ n: Int) -> (Int) -> Int {
    return { x in
        var result = x
        for _ in 0..<n {
            result = f(result)
        }
        return result
    }
}

assert(makeRepeater({ $0 + 1 }, 3)(5) == 8)
assert(makeRepeater({ $0 * 3 }, 5)(1) == 243)
assert(makeRepeater({ $0 * $0 }, 2)(5) == 625)
assert(makeRepeater({ $0 * 3 }, 0)(5) == 5)

f is used inside the returned closure, which outlives the call to makeRepeater, so the parameter must be @escaping.

What’s next

Next lesson: how a returned closure still sees the names around it, using environments, frames, and captured values.