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

Structures and enumerations

Define your own types: structures that bundle data with behavior, the rational-number data abstraction, and enumerations whose cases make invalid states impossible.

CS61A
Objects · Composing Programs 2.4–2.5

Arrays, dictionaries, and tuples combine values, but they do not say what the combination means. A pair of integers could be a point, a fraction, or a date. Swift lets you define new types that carry that meaning, and the behavior that goes with it.

Structures

A structure groups named values, called stored properties, into one type:

struct Point {
    var x: Int
    var y: Int
}

var p = Point(x: 3, y: 4)   // a memberwise initializer, written for you
p.x += 1
print(p.x, p.y)             // 4 4
print(p)                    // Point(x: 4, y: 4)

A structure can also have methods, functions that belong to the type, and computed properties, values calculated each time they are read:

struct Point {
    var x: Int
    var y: Int

    var distanceFromOrigin: Double {
        Double(x * x + y * y).squareRoot()
    }

    func moved(by dx: Int, _ dy: Int) -> Point {
        Point(x: x + dx, y: y + dy)
    }
}

let p = Point(x: 3, y: 4)
print(p.distanceFromOrigin)     // 5.0
print(p.moved(by: 1, 1))        // Point(x: 4, y: 5)

Inside a method, the type’s properties are available by name: x means this point’s x.

Structures are values. Assigning one to a new name copies it, and the two copies are independent from then on:

What would Swift print?

struct Point {
    var x: Int
    var y: Int
}
var a = Point(x: 1, y: 2)
var b = a
b.x = 9
print(a.x, b.x)

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

Show answer and explanation

Answer1 9

var b = a copies the whole structure. Changing b.x does not affect a, so a.x is still 1. Lesson 14 is about when this is exactly what you want, and Lesson 15 about when it is not.

Data abstraction: rational numbers

CS61A’s first data abstraction is the rational number: a fraction with an integer numerator and denominator. The point is not fractions; it is the abstraction barrier. Code that uses rationals should add and multiply them without knowing how they are stored.

func gcd(_ a: Int, _ b: Int) -> Int {
    b == 0 ? abs(a) : gcd(b, a % b)
}

struct Rational {
    let numerator: Int
    let denominator: Int

    init(_ numerator: Int, _ denominator: Int) {
        precondition(denominator != 0, "a denominator cannot be zero")
        let divisor = gcd(numerator, denominator) * (denominator < 0 ? -1 : 1)
        self.numerator = numerator / divisor
        self.denominator = denominator / divisor
    }

    var description: String { "\(numerator)/\(denominator)" }

    func adding(_ other: Rational) -> Rational {
        Rational(numerator * other.denominator + other.numerator * denominator,
                 denominator * other.denominator)
    }

    func multiplied(by other: Rational) -> Rational {
        Rational(numerator * other.numerator, denominator * other.denominator)
    }
}

let half = Rational(1, 2)
let third = Rational(1, 3)
print(half.adding(third).description)          // 5/6
print(half.multiplied(by: third).description)  // 1/6

The custom initializer, init, runs whenever a Rational is made. It puts every fraction in lowest terms with a positive denominator, so 2/4 and -1/-2 both become 1/2. self.numerator means “this value’s property”, distinguishing it from the parameter with the same name. Because the initializer is the only way in, no code anywhere can create a fraction that is not reduced.

What would Swift print?

func gcd(_ a: Int, _ b: Int) -> Int { b == 0 ? abs(a) : gcd(b, a % b) }
struct Rational {
    let numerator: Int
    let denominator: Int
    init(_ n: Int, _ d: Int) {
        let g = gcd(n, d) * (d < 0 ? -1 : 1)
        numerator = n / g
        denominator = d / g
    }
}
let r = Rational(6, -4)
print(r.numerator, r.denominator)

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

Show answer and explanation

Answer-3 2

The greatest common divisor of 6 and −4 is 2, and the negative denominator flips its sign to −2. Dividing both parts by −2 gives −3 and 2.

let properties cannot change after initialization; trying to assign r.numerator = 1 is a compile-time error. A value that cannot be changed cannot be put into an invalid state later.

Enumerations

An enumeration is a type with a fixed list of possible values, its cases:

enum Direction {
    case north, south, east, west
}

let heading = Direction.east
switch heading {
case .north, .south:
    print("vertical")
case .east, .west:
    print("horizontal")
}

Where the type is already known, .east is enough. A switch over an enum must handle every case, so if a new case is added later, the compiler finds every switch that needs updating.

Cases can carry raw values of a fixed type, and enums can have computed properties and methods like structures:

enum Suit: String {
    case spades = "♠", hearts = "♥", diamonds = "♦", clubs = "♣"

    var color: String {
        switch self {
        case .spades, .clubs: "black"
        case .hearts, .diamonds: "red"
        }
    }
}
print(Suit.hearts.rawValue, Suit.clubs.color)   // ♥ black

Associated values

The most powerful feature of Swift’s enums is that each case can carry its own data, called associated values:

enum Shape {
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)

    var area: Double {
        switch self {
        case .circle(let radius):
            Double.pi * radius * radius
        case .rectangle(let width, let height):
            width * height
        }
    }
}

print(Shape.rectangle(width: 2, height: 3).area)   // 6.0

A Shape is either a circle with a radius or a rectangle with a width and height, never a circle with a width. The switch takes the associated values out with let.

What would Swift print?

enum Shape {
    case circle(radius: Double)
    case square(side: Double)
}
func describe(_ s: Shape) -> String {
    switch s {
    case .circle(let r) where r > 10: "big circle"
    case .circle: "circle"
    case .square(let side): "square \(Int(side))"
    }
}
print(describe(.circle(radius: 20)), describe(.circle(radius: 1)), describe(.square(side: 3)))

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

Show answer and explanation

Answerbig circle circle square 3

A where clause adds a condition to a case. The first circle has radius 20, so it matches the first case; the second fails the condition and matches .circle without binding the radius. The square’s side is converted to an Int for printing.

Put the lines in order

Put the lines in order so the program prints red.

  1. print(Suit.diamonds.color)
  2. switch self {
  3. case spades, hearts, diamonds, clubs
  4. }
  5. }
  6. }
  7. case .spades, .clubs: return "black"
  8. var color: String {
  9. case .hearts, .diamonds: return "red"
  10. enum Suit {

Show the correct program
enum Suit {
    case spades, hearts, diamonds, clubs
    var color: String {
        switch self {
        case .spades, .clubs: return "black"
        case .hearts, .diamonds: return "red"
        }
    }
}
print(Suit.diamonds.color)

Lab 11: Your own types

Q1: Subtracting and dividing rationals

Add subtracting(_:) and divided(by:) to Rational. Division by a zero rational should stop the program with a precondition.

Show solution
func gcd(_ a: Int, _ b: Int) -> Int {
    b == 0 ? abs(a) : gcd(b, a % b)
}

struct Rational: Equatable {
    let numerator: Int
    let denominator: Int

    init(_ numerator: Int, _ denominator: Int) {
        precondition(denominator != 0, "a denominator cannot be zero")
        let divisor = gcd(numerator, denominator) * (denominator < 0 ? -1 : 1)
        self.numerator = numerator / divisor
        self.denominator = denominator / divisor
    }

    func adding(_ other: Rational) -> Rational {
        Rational(numerator * other.denominator + other.numerator * denominator,
                 denominator * other.denominator)
    }

    func subtracting(_ other: Rational) -> Rational {
        adding(Rational(-other.numerator, other.denominator))
    }

    func multiplied(by other: Rational) -> Rational {
        Rational(numerator * other.numerator, denominator * other.denominator)
    }

    func divided(by other: Rational) -> Rational {
        precondition(other.numerator != 0, "cannot divide by zero")
        return multiplied(by: Rational(other.denominator, other.numerator))
    }
}

assert(Rational(1, 2).subtracting(Rational(1, 3)) == Rational(1, 6))
assert(Rational(1, 2).divided(by: Rational(1, 4)) == Rational(2, 1))
assert(Rational(3, 4).subtracting(Rational(3, 4)) == Rational(0, 1))

Both new methods reuse the old ones instead of repeating arithmetic. : Equatable asks Swift to generate == for Rational by comparing its properties, which is correct here because every rational is stored reduced. Lesson 16 explains what that declaration means.

Q2: Naming a card

Define an enum Suit with String raw values and a structure Card with a rank: Int (1 to 13) and a suit: Suit. Give Card a computed property name that returns strings like "Queen of hearts", where 1 is Ace, 11 Jack, 12 Queen, and 13 King.

Show solution
enum Suit: String {
    case spades, hearts, diamonds, clubs
}

struct Card {
    let rank: Int
    let suit: Suit

    var name: String {
        let rankName = switch rank {
        case 1: "Ace"
        case 11: "Jack"
        case 12: "Queen"
        case 13: "King"
        default: String(rank)
        }
        return "\(rankName) of \(suit.rawValue)"
    }
}

assert(Card(rank: 12, suit: .hearts).name == "Queen of hearts")
assert(Card(rank: 1, suit: .spades).name == "Ace of spades")
assert(Card(rank: 7, suit: .clubs).name == "7 of clubs")

When raw values are String and none are given, each case’s raw value is its own name. switch can be an expression, like if, when each case is a single value.

Q3: Total area

Using Shape from this lesson, extend it with a triangle(base:height:) case, and write totalArea(_:) for an array of shapes.

Show solution
enum Shape {
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)
    case triangle(base: Double, height: Double)

    var area: Double {
        switch self {
        case .circle(let radius): Double.pi * radius * radius
        case .rectangle(let width, let height): width * height
        case .triangle(let base, let height): base * height / 2
        }
    }
}

func totalArea(_ shapes: [Shape]) -> Double {
    shapes.map { $0.area }.reduce(0, +)
}

assert(totalArea([.rectangle(width: 2, height: 3), .triangle(base: 4, height: 5)]) == 16)
assert(abs(totalArea([.circle(radius: 1)]) - Double.pi) < 1e-12)
assert(totalArea([]) == 0)

Adding the case without updating area would not compile: the switch would no longer be exhaustive. That is the compiler pointing you to the one place that needs the new formula.

What’s next

Next lesson: types that contain themselves, used to build linked lists and trees.