Я думаю, что ключом к пониманию того, что здесь происходит, является различие между вещами, которые определяются динамически во время выполнения, и вещами, которые определяются статически во время компиляции. Не помогает то, что в большинстве языков, таких как Java, протоколы (или интерфейсы) предназначены для получения полиморфного поведения во время времени выполнения, тогда как в Swift протоколы со связанными типами также используются для получения полиморфного поведения. во время компиляции.
Каждый раз, когда вы видите общий заполнитель, такой как T
в вашем примере, то, какой тип заполняется для этого T
, определяется во время компиляции. Итак, в вашем примере:
func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
говорит: returnsSomethingWithAwesomeness
- это функция, которая может работать с любым типом T
, пока T
соответствует HasAwesomeness
.
Но то, что заполняется для T
, определяется в момент вызова returnsSomethingWithAwesomeness
- Swift просмотрит всю информацию на сайте вызова и решит, какой тип T
, и заменит все T
заполнители этим типом. *
Итак, предположим, что на сайте вызова выбрано то, что T
является String
, вы можете думать о returnsSomethingWithAwesomeness
как о переписанном со всеми вхождениями заполнителя 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
другой (все типы поддерживают присвоение), и то есть < / em>.
Вы не можете сравнивать его с другими Т (нет гарантии, что он поддерживает ==
). Вы не можете создавать новые (кто знает, будет ли у 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
на самом деле, может быть заполнено компилятором.
* это небольшое упрощение того, что на самом деле делает компилятор, но оно подходит для этого объяснения.
person
Airspeed Velocity
schedule
10.12.2014