Swift Generics: не удается преобразовать значение типа в ожидаемый тип аргумента

вот мой код:

protocol SomeProtocol {
}

class A: SomeProtocol {
}

func f1<T: SomeProtocol>(ofType: T.Type, listener: (T?) -> Void) {
}

func f2<T: SomeProtocol>(ofType: T.Type, listener: ([T]?) -> Void) {
}

func g() {
    let l1: (SomeProtocol?) -> Void = ...
    let l2: ([SomeProtocol]?) -> Void = ...
    f1(ofType: A.self, listener: l1) // NO ERROR
    f2(ofType: A.self, listener: l2) // COMPILE ERROR: Cannot convert value of type '([SomeProtocol]?) -> Void' to expected argument type '([_]?) -> Void'
}

в чем проблема со вторым замыканием, имеющим аргумент массив объектов универсального типа?

3 ответов


Swift 4.1 Обновление

это ошибка, которая была исправлена в этом запросе pull, что сделает его в выпуске Swift 4.1. Теперь код компилируется, как и ожидалось, в моментальном снимке 4.1.


Pre Swift 4.1

это выглядит так, как будто вы просто растягиваете компилятор слишком далеко.

  • он может заниматься конвертирование массивов суб-типизированных элементов в массивах супер-типизированных элементов, электронная.г [A] к [SomeProtocol] – это ковариация. Стоит отметить, что массивы всегда были реберным случаем здесь, так как произвольные дженерики инвариантны. Некоторые коллекции, такие как Array, просто получить специальное обращение от компилятора С учетом ковариации.

  • она может общаться с преобразованием функции с супер-типизированных параметров в функции с дополнительными параметрами типов, электронные.г (SomeProtocol) -> Void to (A) -> Void – это контравариантность.

однако кажется, что в настоящее время он не может сделать оба за один раз (но на самом деле он должен быть в состоянии; не стесняйтесь ошибка).

для чего это стоит, это не имеет ничего общего с дженериками, следующее воспроизводит то же самое поведение:

protocol SomeProtocol {}
class A : SomeProtocol {}

func f1(listener: (A) -> Void) {}
func f2(listener: ([A]) -> Void) {}
func f3(listener: () -> [SomeProtocol]) {}

func g() {

    let l1: (SomeProtocol) -> Void = { _ in }        
    f1(listener: l1) // NO ERROR

    let l2: ([SomeProtocol]) -> Void = { _ in }
    f2(listener: l2) 
    // COMPILER ERROR: Cannot convert value of type '([SomeProtocol]) -> Void' to
    // expected argument type '([A]) -> Void'

    // it's the same story for function return types
    let l3: () -> [A] = { [] }
    f3(listener: l3)
    // COMPILER ERROR: Cannot convert value of type '() -> [A]' to
    // expected argument type '() -> [SomeProtocol]'
}

пока не исправлено, одно из решений в этом случае-просто использовать выражение закрытия, чтобы действовать как батут между двумя функциями типы:

// converting a ([SomeProtocol]) -> Void to a ([A]) -> Void.
// compiler infers closure expression to be of type ([A]) -> Void, and in the
// implementation,  gets implicitly converted from [A] to [SomeProtocol].
f2(listener: { l2() })

// converting a () -> [A] to a () -> [SomeProtocol].
// compiler infers closure expression to be of type () -> [SomeProtocol], and in the
// implementation, the result of l3 gets implicitly converted from [A] to [SomeProtocol]
f3(listener: { l3() })

и, применительно к вашему коду:

f2(ofType: A.self, listener: { l2() })

это работает, потому что компилятор выводит выражение закрытия типа ([T]?) -> Void, который можно передать в f2. В реализации закрытия компилятор затем выполняет неявное преобразование С [T]? to [SomeProtocol]?.

и как намекает Доминик, этот батут смог также быть сделан как дополнительная перегрузка f2:

func f2<T : SomeProtocol>(ofType type: T.Type, listener: ([SomeProtocol]?) -> Void) {
    // pass a closure expression of type ([T]?) -> Void to the original f2, we then
    // deal with the conversion from [T]? to [SomeProtocol]? in the closure.
    // (and by "we", I mean the compiler, implicitly)
    f2(ofType: type, listener: { (arr: [T]?) in listener(arr) })
}

позволяет вам еще раз назвать его как f2(ofType: A.self, listener: l2).


закрытие слушателя в func f2<T: SomeProtocol>(ofType: T.Type, listener: ([T]?) -> Void) {...} требует, чтобы его аргумент был массивом T, где T-тип, реализующий SomeProtocol. Написав <T: SomeProtocol>, вы обеспечиваете это все элементы этого массива имеют один и тот же тип.

скажем, например, у вас есть два класса: A и B. Оба совершенно различны. Но оба реализуют SomeProtocol. В этом случае входной массив не может быть [A(), B()] из-за ограничений типа. Входной массив может либо будь [A(), A()] или [B(), B()].

но, когда вы определяете l2 as let l2: ([SomeProtocol]?) -> Void = ..., вы позволяете закрытию принять аргумент, такой как [A(), B()]. Следовательно, это закрытие и закрытие, которое вы определяете в f2 несовместимы, и компилятор не может конвертировать между ними.

к сожалению, вы не можете добавить принудительное исполнение типа к переменной, такой как l2 как заявил здесь. Что вы можете сделать, если вы знаете, что l2 будет работать на массивах класса A, вы можете переопределить его следующим образом:

let l2: ([A]?) -> Void = { ... }

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

func greatest<T: Comparable>(array: [T]) -> T {
    // return greatest element in the array
}

теперь, если вы попытаетесь вызвать эту функцию так:

let comparables: [Comparable] = [1, "hello"]
print(greatest(array: comparables))

компилятор будет жаловаться, так как нет способа сравнить int и строку. Вместо этого вы должны сделать следующее:

let comparables: [Int] = [1, 5, 2]
print(greatest(array: comparables))

ничего на ответ Хэмиша, он на 100% прав. Но если вы хотите супер простое решение без каких-либо объяснений или кода просто работать, при работе с массивом протокола generics используйте следующее:

func f1<T: SomeProtocol>(ofType: T.Type, listener: (T?) -> Void) {
}

func f2<Z: SomeProtocol>(ofType: Z.Type, listener: ([SomeProtocol]?) -> Void) {
}