Generic functions and types
Say you need a function that finds the largest number in an array. Then you need the "largest" string, that is, the last one in alphabetical order. The code of the two functions matches letter for letter, and only the type differs: [Int] in one and [String] in the other. Copying the function for every type is inconvenient: a fix in one copy has to be repeated in all the others.
Picture a machine with an adjustable slot. Before work, the slot is fitted to the part, and the machine processes a bolt, a nut or a washer with the very same movements. A plate on the machine states a requirement for the part, for example "metal only". A part that does not meet the requirement will not fit into the slot.
A function or a type with such a slot is called generic: the type of its values is not written in advance but filled in when it is used.
A generic function
Let's write one function for numbers, strings and any other values that can be compared:
func largest<T: Comparable>(_ items: [T]) -> T? {
if items.isEmpty {
return nil
}
var best = items[0]
for item in items {
if item > best {
best = item
}
}
return best
}
print(largest([3, 9, 4]) ?? 0) // 9
print(largest(["pear", "apple"]) ?? "none") // pearLet's take the declaration apart:
<T: Comparable>after the function's name is the slot.Tis a type parameter: a name that a real type takes the place of in each call.: Comparableis the plate on the machine, a constraint: only a type that conforms toComparablefits the slot._ items: [T]and-> T?mean an array of values of that type goes in, and one such value comes out, ornilif the array is empty.- In the call
largest([3, 9, 4])Swift sees an array ofIntand putsIntin place ofT. For["pear", "apple"]it puts inString.
The result's type is not lost: largest([3, 9, 4]) returns an Int?, and ?? 0 supplies a number.
Here the machine picture is inaccurate. A machine is refitted by hand, while nobody sets up a generic function: Swift picks T from the arguments of each call itself.
Why a constraint is needed
Inside largest there is a comparison, item > best. Not every type has the > operator, only Comparable ones, and the constraint promises the compiler that T will have it. The compiler enforces that promise from both sides:
- the function's body may use only what the constraint provides;
- a call with a type that is not
Comparabledoes not compile.
If you pass largest an array of Point structs that are not Comparable, the compiler stops the call:
error: global function 'largest' requires that 'Point' conform to 'Comparable'A generic type
Types can have a slot too. A stack of plates works the same way whatever plates are in it: a plate goes on top and comes off the top.
struct Stack<Element> {
var items: [Element] = []
var top: Element? { items.last }
mutating func push(_ item: Element) {
items.append(item)
}
}
var plates = Stack<String>()
plates.push("blue")
plates.push("red")
print(plates.top ?? "empty", plates.items.count) // red 2What matters here:
Elementis a type parameter, likeT. You may choose any name, as long as it reads well.Stack<String>()gives the type in angle brackets, andpushaccepts only strings.- There is no constraint here: the stack does not need to compare plates, so it can hold values of any type.
You have already used generic types. [Int] is the short form of Array<Int>, and Set<Int> is the full type of a set of numbers.
Common mistake
The constraint was forgotten:
func larger<T>(_ a: T, _ b: T) -> T {
a > b ? a : b // ✗
}error: referencing operator function '>' on 'Comparable' requires that 'T' conform to 'Comparable'The program does not compile, even if larger is only ever called with numbers. The compiler checks the body once and for any T, not for the types of particular calls. Whether any T can be compared with > is unknown, so the comparison is not allowed.
Interviewers like to ask this: why can't a generic function without a constraint compare its arguments? The answer: about an unconstrained T, the compiler knows nothing except that it is a type. The fix is <T: Comparable>.
Key points
- A generic function or type declares a type parameter in angle brackets,
<T>, and Swift fills in the real type each time it is used. - The constraint
<T: Comparable>lets only types with that protocol into the function and allows the body to compare values. - Without a constraint,
a > bin the body does not compile, even if the function is only called withInt. Stack<Element>,Array<Int>andSet<Int>are generic types: one design for elements of any type.