Возврат ограниченных дженериков из функций и методов

Я хотел бы создать функцию, которая возвращает объект, соответствующий протоколу, но протокол использует typealias. Учитывая следующий пример игрушки:

protocol HasAwesomeness {
    typealias ReturnType
    func hasAwesomeness() -> ReturnType
}

extension String: HasAwesomeness {
    func hasAwesomeness() -> String {
        return "Sure Does!"
    }
}

extension Int: HasAwesomeness {
    func hasAwesomeness() -> Bool {
        return false
    }
}

String и Int были расширены, чтобы соответствовать HasAwesomeness, и каждый из них реализует метод hasAwesomeness() для возврата другого типа.

Теперь я хотел бы создать класс, который возвращает объект, соответствующий протоколу HasAwesomeness. Меня не волнует, что это за класс, просто я могу отправить сообщение hasAwesomenss(). Когда я пытаюсь сделать следующее, я генерирую ошибку компиляции:

class AmazingClass: NSObject {
    func returnsSomethingWithAwesomeness(key: String) -> HasAwesomeness {
        ...
    }
}

ОШИБКА: протокол HasAwesomeness можно использовать только в качестве общего ограничения, так как он имеет требования типа Self или связанные с ним.

Как вы понимаете, цель returnsSomethingWithAwesomeness - вернуть String или Int на основе ok параметр key. Ошибка, которую выдает компилятор, имеет смысл, почему она запрещена, но дает представление о том, как исправить синтаксис.

func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
{
    ...
}

Хорошо, я читаю, что метод returnsSomethingWithAwesomeness - это общий метод, который возвращает любой тип T, имеющий подтип HasAwesomness. Но следующая реализация вызывает больше ошибок типа времени компиляции:

func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
{
    if key == "foo" {
        return "Amazing Foo"
    }
    else {
        return 42
    }
}

ОШИБКА: тип "T" не соответствует протоколу StringLiteralConvertible.

ОШИБКА: тип "T" не соответствует протоколу IntegerLiteralConvertible.

Хорошо, теперь я застрял. Может ли кто-нибудь помочь заполнить пробелы в моем понимании типов и обобщений, возможно, указав мне на полезные ресурсы?


person edelaney05    schedule 10.12.2014    source источник
comment
Кто-нибудь может помочь объяснить, почему в Swift 3 следующий код имеет аналогичную ошибку? func paths ‹T: Последовательность, где T.Iterator.Element == Int› () - ›T? {return [Int] ()}   -  person Mingming    schedule 10.08.2016


Ответы (1)


Я думаю, что ключом к пониманию того, что здесь происходит, является различие между вещами, которые определяются динамически во время выполнения, и вещами, которые определяются статически во время компиляции. Не помогает то, что в большинстве языков, таких как 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
comment
У меня довольно похожая проблема, но даже после прочтения вопроса и вашего ответа я все еще не знаю, как правильно решить свою проблему. Не могли бы вы взглянуть на мою проблему: stackoverflow.com/questions/40749161/ - person chriswillow; 23.11.2016
comment
очень хорошо объяснил :) - person Clement Prem; 12.04.2017