Skip to content

Latest commit

 

History

History
103 lines (74 loc) · 8.15 KB

5.5.3 ExistentialTypes.md

File metadata and controls

103 lines (74 loc) · 8.15 KB

Existential Types: any

  1. Swift: Existential Types
  2. 416 WWDC 2016

В Swift есть интересная фича, о которой мало кто знает, но благодаря дружественному интерфейсу, мы постоянно её используем, даже не замечая этого — экзистенциальные типы (existential types).

Экзистенциальные типы можно понимать как некий абстрактный объект, о котором мы ничего не знаем, но примерно понимаем какие свойства он содержит.

let vehicles: [any Vehicle] = [
    Car(),
    Car(),
    Bus(),
]

Зачем нужны экзистенциальные типы?

Идея экзистенциальных типов в Swift пришла из Haskell, в документации которого они описаны как следующий предикат:

x Q(x) P = x Q(x)  P
// где предикат P определяется однозначно, если все запрашиваемые им требования к объекту Q удовлетворены.

Это означает, что определение объекта производится по значению (свойствам), а не его типу. Пока не задумывайтесь о том, как это выглядит в коде. Важнее понять главное отличие экзистенциальных типов от шаблонов (generic):

  • Generics определяет объект по четким ограничениям типов (по типу объекта);
  • Existential определяет объект по его свойствам (в протоколе);

Экзистенциальные типы vs Generics

Из определения видно, что экзистенциальные типы похожи на generic, а именно тем, что заранее конкретный тип объекта не определен. С точки зрения Swift, экзистенциальные типы позволяют строить абстракции над значениями (value-level abstraction) — в этом и кроется отличие от шаблонов, использующих абстракции над типами (type-level abstraction).

На практике мы довольно часто используем экзистенциальные типы. Например, каждый раз, когда ограничиваем параметры ввода и возвращаемое значение функции через протокол:

// Generics-like
func collect<T: AnyProtocol>(value: T, and anotherValue: T) -> [T] {
  return [value, anotherValue]
}
// Existential Types
func collect(value: AnyProtocol, and anotherValue: AnyProtocol) -> [AnyProtocol] {
  return [value, anotherValue]
}

Для нас, как для разработчиков, различие на первый взгляд незаметно, мы просто используем “протокол как тип данных”. Однако, с точки зрения Swift это не тоже самое, что generic.

Дело в том, что generic являются статически-генерируемой структурой. Хотя в исходном коде они позволяют избегать дублирования, компилятор фактически создает явную перегружаемую функцию на каждый используемый им тип:

// generics in source code
func compare<T: Comparable>(value: T, and anotherValue: T) -> Bool {
  return value == anotherValue
}
compare<Int>(value: 5, and: 10)
compare<Double>(value: 10.0, and: 5.0)
// generics after compile (by demand)
func compare(value: Int, and anotherValue: Int) -> Bool {
  return value == anotherValue
}
func compare(value: Double, and anotherValue: Double) -> Bool {
  return value == anotherValue
}

Такой код создается во время компиляции приложения, в то время как экзистенциальные типы являются динамечиски-генерируемыми, то есть их определение происходит во время исполнения кода.

Когда generic не может заменить Existential Types

protocol AnyProtocol {
  associatedtype T
} 

На практике

Они также являются неочевидной конструкцией языка, но имеют высокую важность для будущих фичей Swift. Например, благодаря экзистенциальным типам, параметры могут быть переданы в функцию, основываясь на перечислении списка протоколов (вместо создания класса, реализующего их):

// Existential Complex Types
func isEquals(with value: Collection & Equatable) -> Bool {
  return self === value
}

При такой записи компилятор Swift не сможет определить какой конкретно тип поступает на вход функции. Он знает только то, что этот тип соответствует двум протоколам Collection и Equatable. Аналогичные возможности мы получаем при описании возвращаемого значения.

Ограничения экзистенциального типа

Экзистенциальный тип, созданный с помощью ключевого слова any,не может использовать == оператор для сравнения двух экземпляров экзистенциального типа.

Для компилятора экзистенциальный тип — это просто «коробка», он понятия не имеет, что внутри коробки. Следовательно, компилятор не может провести сравнение, если он не может гарантировать, что содержимое «коробки» имеет один и тот же базовый конкретный тип.

Производительность

Экзистенциалы относительно дороги в использовании, потому что компилятор и среда выполнения не могут заранее определить, сколько памяти должно быть выделено для конкретного объекта, который будет заполнять экзистенциал. Всякий раз, когда вы вызываете метод для экзистенциала, такого как networking свойство в фрагменте кода, который вы видели ранее, среда выполнения должна будет dynamically dispatch этот вызов конкретному объекту, что медленнее, чем static dispatch, которая идет непосредственно к конкретному типу


5.5.2 Opaque Theme | Back To iOSWiki Contents | 5.5.4 Any vs Some Theme