Обобщённые функции и типы
Вам нужна функция, которая находит самое большое число в массиве. Потом нужна ещё самая «большая» строка, то есть последняя по алфавиту. Код у двух функций совпадает до буквы, разный только тип: [Int] в одной и [String] в другой. Копировать функцию под каждый тип неудобно: исправление в одной копии придётся повторять во всех остальных.
Представьте станок с регулируемым гнездом. Перед работой гнездо подгоняют под деталь, и станок обрабатывает болт, гайку или шайбу одними и теми же движениями. На станке висит табличка с требованием к детали, например «Только металл». Деталь, которая требованию не отвечает, в гнездо не встанет.
Функцию или тип с таким гнездом называют обобщёнными: тип значений в них не записан заранее, а подставляется при использовании.
Обобщённая функция
Напишем одну функцию для чисел, строк и любых других значений, которые можно сравнивать:
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") // pearРазберём объявление по частям:
<T: Comparable>после имени функции — гнездо.T— параметр типа: имя, вместо которого при каждом вызове встанет настоящий тип.: Comparable— табличка на станке, или ограничение: в гнездо подходит только тип, который соответствуетComparable._ items: [T]и-> T?— на входе массив значений этого типа, а на выходе одно такое значение илиnil, если массив пуст.- При вызове
largest([3, 9, 4])Swift видит массивIntи подставляетIntвместоT. Для["pear", "apple"]он подставляетString.
Тип результата не теряется: largest([3, 9, 4]) возвращает Int?, и ?? 0 подставляет число.
Здесь картинка со станком неточна. Станок перенастраивают руками, а обобщённую функцию никто не настраивает: Swift сам подбирает T по аргументам каждого вызова.
Зачем нужно ограничение
Внутри largest есть сравнение item > best. Оператор > есть не у каждого типа, а только у Comparable, и ограничение обещает компилятору, что у T этот оператор будет. Компилятор следит за этим обещанием с двух сторон:
- тело функции может пользоваться только тем, что даёт ограничение;
- вызов с типом без
Comparableне скомпилируется.
Если передать в largest массив структур Point без Comparable, компилятор остановит вызов:
error: global function 'largest' requires that 'Point' conform to 'Comparable'Обобщённый тип
Гнездо бывает и у типов. Стопка тарелок устроена одинаково, какие бы тарелки в ней ни лежали: тарелку кладут сверху и сверху же берут.
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 2Что здесь важно:
Element— параметр типа, какT. Имя можно выбрать любое, лишь бы оно читалось.Stack<String>()указывает тип в угловых скобках, иpushпринимает только строки.- Ограничения здесь нет: стопке не нужно сравнивать тарелки, поэтому в неё можно класть значения любого типа.
Обобщёнными типами вы уже пользовались. [Int] — короткая запись Array<Int>, а Set<Int> — полный тип множества чисел.
Частая ошибка
Ограничение забыли:
func larger<T>(_ a: T, _ b: T) -> T {
a > b ? a : b // ✗
}error: referencing operator function '>' on 'Comparable' requires that 'T' conform to 'Comparable'Программа не компилируется, даже если вызывать larger только с числами. Компилятор проверяет тело один раз и для любого T, а не для типов из конкретных вызовов. Умеет ли любой T сравниваться через >, неизвестно, поэтому сравнение запрещено.
Этот вопрос часто задают на собеседовании: почему обобщённая функция без ограничения не может сравнить свои аргументы? Ответ: про T без ограничения компилятор не знает ничего, кроме того, что это тип. Исправление — <T: Comparable>.
Главное
- Обобщённая функция или тип объявляет параметр типа в угловых скобках,
<T>, а Swift подставляет настоящий тип при каждом использовании. - Ограничение
<T: Comparable>пускает в функцию только типы с этим протоколом и разрешает телу сравнивать значения. - Без ограничения
a > bв теле не скомпилируется, даже если функцию вызывают только сInt. Stack<Element>,Array<Int>иSet<Int>— обобщённые типы: одно устройство для элементов любого типа.