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

Names and functions

Bind names with let and var, define functions with argument labels, and see why a function that prints is different from one that returns.

CS61A
Functions · Composing Programs 1.2–1.3
Swift book
The Basics · Functions

Last lesson every value was written out in full. Programs get interesting when we can name things: a value we computed once, or a whole computation we want to repeat. Naming is the first means of abstraction.

Binding names

let binds a name to a value. After that, the name stands for the value:

let radius = 10.0
let pi = 3.14159
let area = pi * radius * radius
print(area)   // 314.159

A name bound with let is a constant: it cannot be rebound. When a value genuinely needs to change, use var for a variable:

var total = 0
total = total + 5
total += 5   // shorthand for total = total + 5
print(total) // 10

Prefer let. When a name cannot change, a reader never has to track what happened to it, and the compiler will tell you if you try.

What would Swift print?

let n = 5
n = 6
print(n)

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

Show answer and explanation

AnswerError

n was bound with let, so n = 6 is a compile-time error: “cannot assign to value: ‘n’ is a ‘let’ constant”. Nothing prints. Declaring it with var would print 6.

Types are inferred

let radius = 10.0 never says “this is a Double”; Swift infers it from the value. You can write the type yourself with an annotation, which is useful when the value alone would suggest a different type:

let count: Double = 3
print(count / 2)   // 1.5, because count is a Double

Defining functions

A function definition gives a name to a computation. It lists the parameters and their types, the type of the result after ->, and a body:

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

print(square(12))   // 144
print(square(square(3)))   // 81

When the body is a single expression, return can be left out: func square(_ x: Int) -> Int { x * x } means the same thing.

Calling a function follows the rule from last lesson: evaluate the arguments, then apply. Applying a user-defined function means binding its parameters to the argument values in a new, local frame, and evaluating the body there. The parameter x exists only while square runs.

What would Swift print?

func square(_ x: Int) -> Int { x * x }
let x = 3
print(square(x + 1), x)

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

Show answer and explanation

Answer16 3

The argument x + 1 is evaluated first, giving 4, and square binds its own parameter x to 4 in its own frame. The global x is untouched, so the second value printed is still 3.

Argument labels

Swift functions have argument labels, names written at the call site. By default the label is the parameter name:

func greet(person: String) -> String {
    "Hello, \(person)!"
}
print(greet(person: "Anna"))

You can give a separate label and parameter name, so the call reads like a sentence while the body uses a short name. An underscore means no label:

func divide(_ dividend: Int, by divisor: Int) -> Int {
    dividend / divisor
}
print(divide(17, by: 5))   // 3

Labels are part of the function’s name. Leaving one out is an error.

What would Swift print?

func increment(value: Int) -> Int { value + 1 }
print(increment(2))

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

Show answer and explanation

AnswerError

The label is value:, so the call must be increment(value: 2). Swift reports “missing argument label ‘value:’ in call”.

Returning several values

A function returns one value, but that value can be a tuple. Labels on the tuple’s elements let callers read them by name:

func divide(_ n: Int, by d: Int) -> (quotient: Int, remainder: Int) {
    (n / d, n % d)
}

let result = divide(17, by: 5)
print(result)             // (quotient: 3, remainder: 2)
print(result.remainder)   // 2

Pure functions and print

Some functions only compute a value: square, max, divide. Calling them twice with the same arguments gives the same result, and nothing else happens. CS61A calls these pure functions.

print is different. Its job is a side effect, writing to the screen, and the value it returns is empty. In Swift that empty value is written () and its type is called Void.

What would Swift print?

print(print("Hi"))

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

Show answer and explanation

AnswerHi\n()

The argument is evaluated first: the inner print("Hi") writes Hi and returns (). Then the outer print writes that value, (). This is the Swift version of a classic CS61A question, where Python prints None.

The difference matters when you build on a function. A function that returns its answer can be used inside a larger expression; one that only prints it cannot.

Put the lines in order

Put the lines in order. The function should return the quotient and remainder; the program should print (3, 2).

  1. let remainder = n - quotient * d
  2. let quotient = n / d
  3. }
  4. func divide(_ n: Int, by d: Int) -> (Int, Int) {
  5. return (quotient, remainder)
  6. print(divide(17, by: 5))

Show the correct program
func divide(_ n: Int, by d: Int) -> (Int, Int) {
    let quotient = n / d
    let remainder = n - quotient * d
    return (quotient, remainder)
}
print(divide(17, by: 5))

Lab 1: Functions

Write each function in a file lab01.swift, with the assert lines from the solution below it, and run swift lab01.swift. No output means every check passed.

Q1: Fahrenheit to Celsius

Write celsius(fromFahrenheit:), which converts a Double temperature using C = (F − 32) × 5 / 9.

func celsius(fromFahrenheit f: Double) -> Double {
    // your code here
}
Show solution
func celsius(fromFahrenheit f: Double) -> Double {
    (f - 32) * 5 / 9
}

assert(celsius(fromFahrenheit: 212) == 100)
assert(celsius(fromFahrenheit: 32) == 0)
assert(celsius(fromFahrenheit: -40) == -40)

The label fromFahrenheit makes the call read as English, while the body uses the short parameter name f.

Q2: Two of three

Write twoOfThree(_:_:_:), which takes three positive integers and returns the sum of the squares of the two smallest. Use a single expression in the body. min and max may help.

func twoOfThree(_ i: Int, _ j: Int, _ k: Int) -> Int {
    // your code here
}
Show solution
func twoOfThree(_ i: Int, _ j: Int, _ k: Int) -> Int {
    i * i + j * j + k * k - max(i, j, k) * max(i, j, k)
}

assert(twoOfThree(1, 2, 3) == 5)
assert(twoOfThree(5, 3, 1) == 10)
assert(twoOfThree(10, 2, 8) == 68)
assert(twoOfThree(5, 5, 5) == 50)

Adding all three squares and subtracting the largest avoids working out which two are the smallest. Another single expression is min(i, j, k) * min(i, j, k) + ..., but finding the middle value is harder than removing the largest.

Q3: Print or return?

Here are two versions of the same function. Without running them, decide what each program prints. Then run them to check.

func shout(_ word: String) {
    print(word.uppercased())
}
func shouted(_ word: String) -> String {
    word.uppercased()
}

shout("hi")
print(shouted("hi") + "!")
Show solution

It prints HI and then HI!. shout writes the result itself and returns nothing, so it cannot be used inside +. shouted returns its result, so the caller can decide what to do with it: add an exclamation mark, compare it, or print it. Prefer the returning version unless printing is the function’s whole job.

What’s next

Next lesson: choosing between options with if and switch, and repeating work with loops.