2014-12-10 2 views
25

Я хотел бы создать функцию, которая возвращает объект, соответствующий протоколу, но протокол использует 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 { 
     ... 
    } 
} 

ERROR: Protocol 'HasAwesomeness' can only be used as a generic constraint because it has Self or associated type requirements

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

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 
    } 
} 

ERROR: Type 'T' does not conform to protocol 'StringLiteralConvertible'

ERROR: Type 'T' does not conform to protocol 'IntegerLiteralConvertible'

Alright, так что теперь я застрял. Кто-нибудь, пожалуйста, помогите заполнить пробелы в моем понимании типов и дженериков, возможно, указывая на полезные ресурсы?

+0

Любой может помочь объяснить, почему в Swift 3 следующий код имеет схожую ошибку? func paths () -> T? { return [Int]() } – Mingming

ответ

58

Я думаю, что ключом к пониманию того, что здесь происходит, является различие между вещами, которые динамически определяются во время выполнения, и вещами, которые статически определяются во время компиляции. Это не помогает, что на большинстве языков, таких как 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 другой (назначение поддержки всех типов) и , то есть.

Вы не можете сравнить это с другими Ц (без гарантии, что он поддерживает ==). Вы не можете создавать новые (кто знает, будет ли у 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 действительно может быть заполнено компилятором.

* Это небольшое упрощение того, что на самом деле делает компилятор, но это будет сделано для этого объяснения.

+0

У меня есть довольно схожая проблема, но даже после прочтения вопроса и вашего ответа я до сих пор не знаю, как правильно решить мою проблему.Не могли бы вы взглянуть на мою проблему: http://stackoverflow.com/questions/40749161/how-do-implement-a-swift-protocol-with-a-generic-constrained-type-property – KaraBenNemsi

+0

очень хорошо объяснил :) –

Смежные вопросы