Сравните ограничения на типы коллекций: почему вызов этой функции допустим для Array и Set, хотя сами типы коллекций различаются?
func combined<A: Collection, B: Collection>(
_ first: A,
_ second: B
) -> [A.Element]
where A.Element == B.Element {
Array(first) + Array(second)
}
let result = combined([1, 2], Set([3, 4]))
Вызов допустим, потому что ограничение A.Element == B.Element требует одинаковый тип элементов, но не одинаковый тип самих коллекций. В примере A — Array<Int>, B — Set<Int>, поэтому условие выполнено, а результат имеет тип [Int].
Это same-type constraint между associated types двух независимых generic-параметров. Оно связывает только A.Element и B.Element, оставляя A и B разными конкретными типами.
Обобщённые алгоритмы часто должны работать не с одним конкретным типом, а с несколькими абстракциями, между которыми существует известная связь. Ограничений вида A: Collection и B: Collection недостаточно, если алгоритму нужно безопасно объединять элементы этих коллекций.
Same-type constraints решают эту проблему декларативно: разработчик описывает отношение между типами, а компилятор проверяет его при каждом вызове. Благодаря этому не требуется приводить значения к общему типу во время выполнения.
Если объявить параметры только как A: Collection и B: Collection, компилятор не сможет считать A.Element и B.Element одним типом. Например, первая коллекция может содержать Int, а вторая — String; операция объединения в [A.Element] тогда была бы некорректной.
Обратная крайность — потребовать A == B. Это гарантировало бы совместимость, но запретило бы полезные вызовы с разными коллекциями, например с массивом и множеством. Такое ограничение сделало бы API чрезмерно узким.
В объявлении функции участвуют два независимых параметра типа:
A: Collection означает, что A — конкретный тип коллекции;B: Collection означает то же для B;A.Element == B.Element требует идентичности типов элементов.При вызове combined([1, 2], Set([3, 4])) компилятор выводит A как Array<Int>, а B как Set<Int>. Их associated types совпадают: A.Element и B.Element — это Int.
После проверки ограничения тело функции может использовать B.Element там, где ожидается A.Element: компилятор знает, что это один и тот же тип. Преобразование в Array нужно только для получения конкретного результирующего типа и не меняет сути generic-ограничения.
Если передать [Int] и [String], вызов не скомпилируется: условие A.Element == B.Element нарушено. При этом порядок элементов Set не гарантируется, поэтому результат для второй части примера может иметь любой порядок элементов множества.
Такое ограничение не означает, что типы A и B равны. Оно также не означает совместимость через наследование или неявное приведение: требуется именно один и тот же тип элементов.
В модуле синхронизации нужно объединять локальные записи из массива и записи из множества, не дублируя алгоритм для каждого вида коллекции. Оба источника содержат Record, но имеют разные свойства: массив сохраняет порядок, множество обеспечивает уникальность.
Вариант с отдельными перегрузками для Array и Set прост для двух типов, но плохо масштабируется: для каждой новой коллекции потребуется новый API. Вариант с [Any] принимает любые значения, однако теряет статическую типобезопасность и вынуждает выполнять проверки или приведения элементов.
Выбранная generic-функция с A.Element == B.Element сохраняет статическую проверку и принимает любые две коллекции с одинаковым типом элементов. В результате один алгоритм работает с Array, Set, Slice и пользовательскими коллекциями без дублирования кода.
A == B, чтобы объединить коллекции?Нет. A == B потребовало бы, чтобы обе переменные имели один и тот же конкретный тип коллекции. Для задачи объединения достаточно A.Element == B.Element; именно это сохраняет возможность передать, например, Array<Int> и Set<Int>.
A.Element == B.Element вызов для Int и NSNumber?Нет, если это разные типы в конкретном контексте. Same-type constraint проверяет идентичность типов, а не наличие преобразования между ними и не похожесть их интерфейсов. Для объединения таких значений нужно явно преобразовать элементы к выбранному общему типу до вызова или внутри отдельного алгоритма с соответствующим результатом.
A.Element: Equatable для этой функции?Нет, простое последовательное объединение не сравнивает элементы. Ограничение Equatable понадобится, если реализация будет удалять дубликаты, искать элементы через == или использовать структуру данных, требующую сравнения. Лишнее ограничение уменьшило бы множество допустимых типов без пользы для текущего алгоритма.