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