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

Optionals

Represent a value that might be missing with an optional, and unwrap it safely with if let, guard let, ??, and optional chaining.

CS61A
Containers (Swift-specific)

Some questions have no answer. The first element of an empty array. The number written in the string "four". The value for a key that is not in a dictionary. Python answers these with None, and a program that forgets to check crashes much later with a confusing error.

Swift makes “might be missing” part of the type. That is what the ? and ! in the last two lessons were about.

Optional types

Int? is an optional Int: either an Int, or nil, meaning no value. Int("42") returns an Int? because the conversion can fail:

let parsed = Int("42")    // Int?, holding 42
let failed = Int("four")  // Int?, holding nil
print(parsed as Any, failed as Any)   // Optional(42) nil

An Int? is not an Int, and Swift will not let you use it as one:

What would Swift print?

let n: Int? = 5
print(n + 1)

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

Show answer and explanation

AnswerError

n might be nil, and nil + 1 has no meaning, so Swift refuses to compile it: “value of optional type ‘Int?’ must be unwrapped”. Before using the number, the program has to say what happens when there isn’t one.

That compile-time error is the whole point. Every place a value might be missing is visible in the code, and the compiler makes you handle it.

Unwrapping

if let

if let runs a block only when the optional holds a value, binding that value to a name of the non-optional type:

let input = "42"
if let number = Int(input) {
    print(number + 1)          // number is an Int here
} else {
    print("not a number")
}

When the new name is the same as the optional’s, if let number is enough. Several optionals can be unwrapped at once, with extra conditions:

let first = Int("3"), second = Int("x")
if let first, let second {
    print(first + second)
} else {
    print("missing")           // second is nil
}

guard let

guard let unwraps for the rest of the function, and requires the else branch to leave. It keeps the normal path unindented:

func greet(_ name: String?) -> String {
    guard let name else {
        return "Hello, stranger"
    }
    return "Hello, \(name)"
}
print(greet("Ada"), "/", greet(nil))

?? and !

?? supplies a default when the optional is nil:

What would Swift print?

let a = Int("3")
let b = Int("x")
print(b ?? a ?? 0, (b ?? 1) + 1)

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

Show answer and explanation

Answer3 2

b is nil, so b ?? a ?? 0 falls through to a, which is 3. In the second expression, b ?? 1 is 1, and adding 1 gives 2.

! force-unwraps: it takes the value out, and stops the program if there isn’t one. It is a promise to the compiler that you know better.

What would Swift print?

let empty: [Int] = []
print(empty.first!)

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

Show answer and explanation

AnswerError

empty.first is nil, and force-unwrapping nil is a runtime error: “Unexpectedly found nil while unwrapping an Optional value”. Use ! only when nil is truly impossible, and prefer if let or ?? otherwise.

Optional chaining

?. calls a method or reads a property only if the value is there. If anything along the chain is nil, the whole chain is nil:

let words = ["swift", "optional"]
print(words.first?.uppercased() ?? "none")   // SWIFT

let nothing: [String] = []
print(nothing.first?.uppercased() ?? "none") // none

map does the same for a single transformation, and compactMap applies a function that returns optionals to a whole sequence, keeping only the values:

print(Int("7").map { $0 * 2 } ?? 0)              // 14
print(["1", "two", "3"].compactMap { Int($0) })   // [1, 3]

What would Swift print?

let scores = ["Ada": [90, 85]]
print(scores["Ada"]?.first ?? 0, scores["Alan"]?.first ?? 0)

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

Show answer and explanation

Answer90 0

scores["Ada"] is an optional array; ?.first reads its first element, 90. scores["Alan"] is nil, so the whole chain is nil and ?? gives 0.

Put the lines in order

Put the lines in order. The function returns nil when dividing by zero; the program prints 3 and then -1.

  1. }
  2. func safeDivide(_ a: Int, _ b: Int) -> Int? {
  3. print(safeDivide(1, 0) ?? -1)
  4. print(safeDivide(7, 2) ?? -1)
  5. return a / b
  6. guard b != 0 else {
  7. }
  8. return nil

Show the correct program
func safeDivide(_ a: Int, _ b: Int) -> Int? {
    guard b != 0 else {
        return nil
    }
    return a / b
}
print(safeDivide(7, 2) ?? -1)
print(safeDivide(1, 0) ?? -1)

Lab 10: Optionals

Q1: Safe division

Write safeDivide(_:_:), returning nil when the divisor is zero and the integer quotient otherwise.

func safeDivide(_ a: Int, _ b: Int) -> Int? {
    // your code here
}
Show solution
func safeDivide(_ a: Int, _ b: Int) -> Int? {
    b == 0 ? nil : a / b
}

assert(safeDivide(7, 2) == 3)
assert(safeDivide(1, 0) == nil)
assert(safeDivide(0, 5) == 0)

An Int? can be compared with == against a number or nil directly.

Q2: The first repeat

Write firstRepeated(_:), which returns the first value that appears for a second time in the array, or nil if every value is different.

func firstRepeated(_ values: [Int]) -> Int? {
    // your code here
}
Show solution
func firstRepeated(_ values: [Int]) -> Int? {
    var seen: Set<Int> = []
    for value in values {
        if seen.contains(value) {
            return value
        }
        seen.insert(value)
    }
    return nil
}

assert(firstRepeated([3, 1, 4, 1, 5, 3]) == 1)   // 1 repeats before 3 does
assert(firstRepeated([1, 2, 3]) == nil)
assert(firstRepeated([]) == nil)

Q3: Sum of the numbers

Write sumOfNumbers(_:), which adds up the strings in an array that are valid integers and ignores the rest. Use no loops.

func sumOfNumbers(_ strings: [String]) -> Int {
    // your code here
}
Show solution
func sumOfNumbers(_ strings: [String]) -> Int {
    strings.compactMap { Int($0) }.reduce(0, +)
}

assert(sumOfNumbers(["1", "two", "3"]) == 4)
assert(sumOfNumbers(["-5", "5", "five"]) == 0)
assert(sumOfNumbers([]) == 0)

compactMap does the unwrapping: failed conversions are nil and are dropped, and only the numbers reach reduce.

What’s next

Next lesson: defining your own types with structures and enumerations.