Functions and argument labels
Why Swift function calls read like sentences, and the parameter features that make that possible.
By the end you will be able to
- Write functions with external and internal parameter names, defaults and variadics
- Explain why parameters are constants and what `inout` changes
- Name a function the way Apple's own API guidelines would
Given func move(from start: Int, to end: Int), which call compiles?
The two-name idea
func greet(person name: String, from town: String) -> String {
"Hello \(name)! Glad you could visit from \(town)."
}
greet(person: "Ada", from: "London")
person and from are what callers write. name and town are what the body uses. The point is that the two audiences want different things: the call site wants to read like English, the body wants short working names.
If you write only one name, it serves as both:
func square(number: Int) -> Int { number * number }
square(number: 4)
And _ removes the label entirely, for cases where it adds nothing:
func square(_ number: Int) -> Int { number * number }
square(4)Single-expression functions omit return
func square(_ n: Int) -> Int { n * n } // implicit return
func describe(_ n: Int) -> String {
let parity = n % 2 == 0 ? "even" : "odd"
return "\(n) is \(parity)" // explicit — more than one statement
}
Only bodies consisting of exactly one expression get the shortcut. Anything longer needs return.
Default values
func makeCoffee(size: String = "medium", shots: Int = 1, decaf: Bool = false) -> String {
"\(size) coffee, \(shots) shot(s)\(decaf ? ", decaf" : "")"
}
Callers supply only what differs from the default, in the declared order:
Defaults are why Swift needs far fewer overloads than Objective-C or Java. One function with sensible defaults replaces five near-identical ones.
Variadic parameters
func total(_ numbers: Int...) -> Int {
numbers.reduce(0, +)
}
total(1, 2, 3) // 6
total() // 0
Inside the function, numbers is an ordinary [Int].
Parameters are constants
This catches people out:
func addSuffix(to name: String) -> String {
// name += "!" ← does not compile; parameters are `let`
var result = name
result += "!"
return result
}
Parameters cannot be reassigned. If you want a mutable working copy, make one. This is not busywork — imagine the mutable world: name gets trimmed and lowercased on line 2 "for convenience", and on line 14 an error message reports "could not find user \(name)" — quietly printing the mutated value, not what the caller sent, while someone debugs the wrong input for an afternoon. Parameters being let means that line can never lie: a parameter always still holds exactly what the caller passed.
inout — deliberately changing the caller's value
When a function's job is to modify what it was given, say so:
func doubleInPlace(_ value: inout Int) {
value *= 2
}
var score = 21
doubleInPlace(&score) // the & is required at the call site
print(score) // 42
The & at the call site is the point. A reader can see, without opening the function, that this call may change score.
Functions are values
A function can be stored, passed, and returned like any other value. Its type is written (Int, Int) -> Int.
func add(_ a: Int, _ b: Int) -> Int { a + b }
let operation: (Int, Int) -> Int = add
print(operation(2, 3)) // 5
This is what makes map, filter and sorted possible — they take a function as an argument. The next lesson is entirely about that.
applyTwice takes a function and applies it to its own result. What does this print?
func applyTwice(_ transform: (Int) -> Int, to value: Int) -> Int {
transform(transform(value))
}
func increment(_ n: Int) -> Int { n + 1 }
func double(_ n: Int) -> Int { n * 2 }
print(applyTwice(increment, to: 5))
print(applyTwice(double, to: 5))Naming, the Apple way
Swift's API Design Guidelines are unusually specific, and following them makes your code look like it belongs next to the frameworks.
- Clarity at the point of use beats brevity. The call site is read far more often than the declaration.
- Methods that do something read as verb phrases:
list.sort(),view.removeFromSuperview(). - Methods that return something read as noun phrases:
list.sorted(),text.uppercased(). - Mutating/non-mutating pairs use ed/ing:
sort()/sorted(),reverse()/reversed(). - Do not repeat type information that is already in the signature.
remove(at: Int), notremoveElementAtIndex(index: Int).
func calc(_ a: [Double], _ b: Bool) -> Double
func doIt(x: Int, y: Int)
func getUserDataFromServer(id: String)func average(of values: [Double], ignoringZeros: Bool) -> Double
func move(from start: Int, to end: Int)
func fetchUser(id: String)Read the right-hand calls aloud: "average of values ignoring zeros", "move from start to end", "fetch user id". Now try the left ones. That difference is what "clarity at the point of use" means in practice.
Which pair follows Swift's naming convention for a mutating method and its non-mutating counterpart?
Name it properly
Rewrite this function so the call site reads naturally. Callers should be able to write clamp(12, to: 1...10).
Defaults instead of overloads
Write one format(_:) function that covers all three calls below, using default parameter values.
Mutate on purpose
Write trimAll(_:) that removes leading and trailing whitespace from every string in an array, changing the caller's array in place.
Design the signature — just the signature — for a function that sends a message to a list of recipients, optionally scheduled for later, optionally with attachments, and returns whether it succeeded. Write the call site you want first, then work backwards to the declaration.
You can now:
- Use external labels, internal names,
_, defaults and variadics deliberately - Explain why parameters are constants and when
inoutis the right tool - Pass functions as values
- Name a function the way Swift's own guidelines would
Next up: closures — functions without names, and the syntax that makes SwiftUI possible.