Ответ 1
Я думаю, что ключом к пониманию того, что здесь происходит, является различение вещей, которые динамически определяются во время выполнения, и вещи, которые статически определяются во время компиляции. Это не помогает, что на большинстве языков, таких как Java, протоколы (или интерфейсы) связаны с получением полиморфного поведения во время выполнения, тогда как в Swift протоколы со связанными типами также используются для получения полиморфного поведения во время компиляции.
Всякий раз, когда вы видите общий заполнитель, например T
в вашем примере, какой тип заполняется для этого T
, определяется во время компиляции. Итак, в вашем примере:
func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
говорит: returnsSomethingWithAwesomeness
- это функция, которая может работать на любом типе T
, только если T
соответствует HasAwesomeness
.
Но то, что заполняется для T
, определяется в точке returnsSomethingWithAwesomeness
, называется - Swift будет просматривать всю информацию на сайте вызова и решать, какой тип T
, и заменить все T
заполнители с этим типом. *
Итак, предположим, что на сайте вызова выбор T
- это String
, вы можете думать о returnsSomethingWithAwesomeness
как переписываемом со всеми вхождениями placeholder T
, замененными на String
:
// giving the type of s here fixes T as a String
let s: String = returnsSomethingWithAwesomeness("bar")
func returnsSomethingWithAwesomeness(key: String) -> String {
if key == "foo" {
return "Amazing Foo"
}
else {
return 42
}
}
Примечание. T
заменяется на String
, а не с типом HasAwesomeness
. HasAwesomeness
используется только как ограничение, т.е. ограничение возможных типов T
.
Когда вы смотрите на это так, вы можете видеть, что return 42
в else
не имеет смысла - как вы могли бы вернуть 42 из функции, которая возвращает строку?
Чтобы returnsSomethingWithAwesomeness
мог работать с тем, что заканчивается T
, Swift ограничивает использование только тех функций, которые гарантированно будут доступны из заданных ограничений. В этом случае все, что мы знаем о T
, это то, что оно соответствует HasAwesomeness
. Это означает, что вы можете вызвать метод returnsSomethingWithAwesomeness
на любом T
или использовать его с другой функцией, которая ограничивает тип HasAwesomeness
или назначает одну переменную типа T
другой (все назначения поддержки типов) и все.
Вы не можете сравнить его с другими Ts (нет гарантии, что он поддерживает ==
). Вы не можете создавать новые (кто знает, будет ли T
иметь соответствующий метод инициализации?). И вы не можете создать его из строкового или целочисленного литерала (для этого потребуется T
соответствовать либо StringLiteralConvertible
, либо IntegerLiteralConvertible
, что не обязательно - следовательно, эти две ошибки при попытке создать тип, используя один из эти виды литералов).
Можно написать общие функции, которые возвращают общий тип, который все соответствует протоколу. Но то, что будет возвращено, будет конкретным типом, а не протоколом, поэтому тип не будет определяться динамически. Например:
func returnCollectionContainingOne<C: ExtensibleCollectionType where C.Generator.Element == Int>() -> C {
// this is allowed because the ExtensibleCollectionType procol
// requires the type implement an init() that takes no parameters
var result = C()
// and it also defines an `append` function that allows you to do this:
result.append(1)
// note, the reason it was possible to give a "1" as the argument to
// append was because of the "where C.Generator.Element == Int" part
// of the generic placeholder constraint
return result
}
// now you can use returnCollectionContainingOne with arrays:
let a: [Int] = returnCollectionContainingOne()
// or with ContiguousArrays:
let b: ContiguousArray = returnCollectionContainingOne()
Подумайте о returnCollectionContainingOne
в этом коде, поскольку в действительности это две функции, одна из которых реализована для ContiguousArray
и одна для Array
, написанная автоматически компилятором в точке, которую вы вызываете (и, следовательно, где она может исправить C
для определенного типа). Не одна функция, которая возвращает протокол, а две функции, возвращающие два разных типа. Таким образом, таким же образом returnsSomethingWithAwesomeness
не может возвращать либо String
, либо Int
во время выполнения на основе некоторого динамического аргумента, вы не могли бы написать версию returnCollectionContainingOne
, которая вернула бы массив или смежный массив. Все, что он может вернуть, это T
, и во время компиляции любой T
действительно может быть заполнен компилятором.
* это небольшое упрощение того, что делает компилятор, но он делает это для объяснения.