Swift Generics: unable to convert type value to expected argument type

Here is my code:

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

What is the problem with the second closure having an array argument objects of universal type?

+5
source share
3 answers

Swift 4.1 Update

This is a bug that was fixed in this migration request , which will lead to the release of Swift 4.1. Your code now compiles as expected in snapshot 4.1.


Pre Swift 4.1

It looks like you're just stretching the compiler too much.

  • It can handle conversions from arrays of subtypes of elements to arrays of supertype elements, for example [A] to [SomeProtocol] - this is covariance. It is worth noting that arrays have always been an edge case here, since arbitrary generics are invariant. Some collections, such as Array , simply receive special processing from the compiler that allows covariance.

  • It can deal with transformations of functions with supertyped parameters to functions with subtyped parameters, for example (SomeProtocol) -> Void to (A) -> Void - this is contravariance.

However, it seems that at present he cannot do this at a time (but in fact he should be able to: indicate an error ).

For what it's worth, it has nothing to do with generics, the following reproduces the same behavior:

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

Before a fixed solution, in this case, you just need to use a closure expression to act like a trampoline between two types of functions:

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

And, as applied to your code:

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

This works because the compiler outputs a closure expression of type ([T]?) -> Void , which can be passed to f2 . When implementing closure, does the compiler then implicitly convert $0 from [T]? in [SomeProtocol]? .

And, as Dominic hints , this trampoline could also be done as an extra f2 overload:

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

Lets you call it f2(ofType: A.self, listener: l2) again f2(ofType: A.self, listener: l2) .

+6
source

Closing a listener in func f2<T: SomeProtocol>(ofType: T.Type, listener: ([T]?) -> Void) {...} requires that its argument be an array of T , where T is a type that implements SomeProtocol . By writing <T: SomeProtocol> , you ensure that all elements of this array are of the same type.

Say, for example, you have two classes: A and B Both of them are completely different. However, both options implement SomeProtocol . In this case, the input array cannot be [A(), B()] due to the type constraint. The input array can be [A(), A()] or [B(), B()] .

But when you define l2 as let l2: ([SomeProtocol]?) -> Void = ... , you allow the closure to take an argument, for example [A(), B()] . Therefore, this closure and the closure that you define in f2 are incompatible and the compiler cannot convert between them.

Unfortunately, you cannot add type coercion to a variable such as l2 , as indicated here . What you can do if you know that l2 will work with class A arrays, you can override it as follows:

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

Let me try to explain this with a simpler example. Let's say you write a generic function to find the largest element in an array of compared values:

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

Now, if you try to call this function as follows:

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

The compiler will complain, since there is no way to compare Int and string. Instead, you should do the following:

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

Do not answer anything to Hamish, he is 100% right. But if you want a simple solution without any explanation or code, just work with the generics protocol array, use this:

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

Source: https://habr.com/ru/post/1269642/


All Articles