I've the following generic function (not part of any class):
func execFuncWithGenericParameter<T, U>(f: (T) -> U){
print(f("Hello World"))
}
I'd like to call this function in with a closure like this:
execFuncWithGenericParameter(f: { (p: String) -> Int in
print(p)
return 4711
})
But the compiler (iPad Swift Playground) tells me that "(String) -> U is not convertible to (T) -> U".
Naturelly I've done done some investigation and was the opinion that the Compiler automatically will infer the types.
Thanks.
The types are being inferred as far as the nature of f: (T) -> U is concerned. The problem is the call to f inside your first method.
Ask yourself: what if T were not String? Then f("Hello world") would be illegal. That is the quandary you've left the compiler with — and it rightly refuses to deal with it.
Here's a legal version:
func execFuncWithGenericParameter<T,U>(f: ((T) -> U), param:T){
f(param)
}
execFuncWithGenericParameter(f: { (p: String) -> Int in
print(p)
return 4711
}, param:"Hello")
Now the first method knows that param will be a T, so all is well. And in the second method, when we call the first method, T is String and param is a String, so it compiles (and runs, and prints).
Related
// Xcode 11.6 / Swift 5
import Foundation
func f<T>(_: (T) -> Void) { }
#discardableResult
func g(_: Int) -> Int { 0 }
f { g($0) } // compiles fine
f { let _ = g($0) } // Generic parameter 'T' could not be inferred
In the above code, the generic function f expects as its argument a function that takes an argument of type T.
The function g takes an argument of type Int.
When I write f { g($0) }, the code compiles. I believe (please correct me if I'm wrong) this compiles because the compiler can infer that T is an Int based on g's argument type.
However, when I try to do something with the return value of g, for example in the let _ = g($0) line, the compiler complains that it can no longer infer the type of T.
It seems to me the return type of g should have no bearing on how the compiler infers T's type, but clearly it does.
Can anyone shed some light on why this happens, and what (if anything) can be done to correct it?
This may or may not be a compiler bug.
It is known that Swift does not try to infer the types of some closures, namely, multi-statement ones, as said in SR-1570:
This is correct behavior: Swift does not infer parameter or return types from the bodies of multi-statement closures.
However, your closure consists of only one statement, one declaration to be specific. It is possible, albeit weird, that they designed it so that Swift doesn't try to infer types if the closure contains one declaration as well. For example, this does not compile either
f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations
If this were by-design, the rationale behind it might be because a single declaration in a closure doesn't make much sense anyway. Whatever is declared, won't be used.
But again, this is just speculation, and this could be a bug as well.
To fix it, simply make it a not-a-declaration:
f { _ = g($0) } // this, without the "let", is IMO the idiomatic way to ignore the function result
Or
f { g($0) } // g has #discardableResult anyway, so you don't even need the wildcard
The function f takes in a function as a parameter which in turn takes a parameter of type T and returns nothing (Void). For a closure to infer types automatically, it has to consist of single (and sometimes simple) expression. Anything complex makes it difficult for the compiler to infer (which makes sense from the compiler's standpoint). Apparently, let _ = g($0) is a complex statement as far as the compiler is concerned. For further information, see this mailing list discussion
it looks like #discardableResult gives you an ability to have 2 types of functions:
g(_: Int) -> Int and g(_: Int) -> Void (it is when you don't want to use a result of function)
I think that
f { g($0) } - here your f can infer a type because it has the same Type
(_: (T) -> Void) and (_: (Int) -> Void)
f { let _ = g($0) } - in this case the type of g function is different from f function
(_: (T) -> Void) and (_: (Int) -> Int)
If you will remove "let" it will compile again:
f { _ = g($0) }
I think that
f { let _ = g($0) } - return only Int value
f { _ = g($0) } - return function (_: (Int) -> Int)
Maybe it is a key here
Please consider the following code:
protocol P {}
class X {}
class Y: P {}
func foo<T>(_ closure: (T) -> Void) { print(type(of: closure)) }
func foo<T>(_ closure: (T) -> Void) where T: P { print(type(of: closure)) }
let xClosure: (X?) -> Void = { _ in }
foo(xClosure) // prints "(Optional<X>) -> ()"
let yClosure: (Y?) -> Void = { _ in }
foo(yClosure) // prints "(Y) -> ()"
Why does the foo(yClosure) call resolve to the version of foo constrained to T: P?
I understand why that version prints what it prints,
what I don't see is why it gets called instead of the other one.
To me it seems that the non-P version would be a better match for T == (Y?) -> Void.
Sure, the constrained version is more specific, but it requires conversion
(an implicit conversion from (Y?) -> Void to (Y) -> Void),
while the non-P version could be called with no conversion.
Is there a way to fix this code in a way such that the P-constrained version gets called
only if the parameter type of the passed-in closure directly conforms to P,
without any implicit conversions?
Specificity seems to always trump variance conversions, according to my experiments. For example:
func bar<T>(_ x: [Int], _ y: T) { print("A") }
func bar<T: P>(_ x: [Any], _ y: T) { print("B") }
bar([1], Y()) // A
bar is more specific, but requires a variance conversion from [Int] to [Any].
For why you can convert from (Y?) -> Void to (P) -> Void, see this. Note that Y is a subtype of Y?, by compiler magic.
Since it is so consistent, this behaviour seems to be by design. Since you can't really make Y not a subtype of Y?, you don't have a lot of choices if you want to get the desired behaviour.
I have this work around, and I admit it's really ugly - make your own Optional type. Let's call it Maybe<T>:
enum Maybe<T> {
case some(T)
case none
// implement all the Optional methods if you want
}
Now, your (Maybe<Y>) -> Void won't be converted to (P) -> Void. Normally I wouldn't recommend this, but since you said:
in the real-world code where I encountered this, the closure has multiple params, any of them can be optional, so this would lead to a combinatorial explosion.
I thought reinventing Optional might be worth it.
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 argument of an array of generic type objects?
Swift 4.1 Update
This is a bug that was fixed in this pull request, which will make it into the release of Swift 4.1. Your code now compiles as expected in a 4.1 snapshot.
Pre Swift 4.1
This just looks like you're just stretching the compiler too far.
It can deal with conversions from arrays of sub-typed elements to arrays of super-typed elements, e.g [A] to [SomeProtocol] – this is covariance. It's worth noting that arrays have always been an edge case here, as arbitrary generics are invariant. Certain collections, such as Array, just get special treatment from the compiler allowing for covariance.
It can deal with conversions of functions with super-typed parameters to functions with sub-typed parameters, e.g (SomeProtocol) -> Void to (A) -> Void – this is contravariance.
However it appears that it currently cannot do both in one go (but really it should be able to; feel free to file a bug).
For what it's worth, this has nothing to do with generics, the following reproduces the same behaviour:
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]'
}
Until fixed, one solution in this case is to simply use a closure expression to act as a trampoline between the two function types:
// 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, applied to your code:
f2(ofType: A.self, listener: { l2($0) })
This works because the compiler infers the closure expression to be of type ([T]?) -> Void, which can be passed to f2. In the implementation of the closure, the compiler then performs an implicit conversion of $0 from [T]? to [SomeProtocol]?.
And, as Dominik is hinting at, this trampoline could also be done as an additional overload of 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) })
}
Allowing you to once again call it as f2(ofType: A.self, listener: l2).
The listener closure in func f2<T: SomeProtocol>(ofType: T.Type, listener: ([T]?) -> Void) {...} requires its argument to be an array of T, where T is a type that implements SomeProtocol. By writing <T: SomeProtocol>, you are enforcing that all elements of that array are of the same type.
Say for example you have two classes: A and B. Both are completely distinct. Yet both implement SomeProtocol. In this case, the input array cannot be [A(), B()] because of the type constraint. The input array can either be [A(), A()] or [B(), B()].
But, when you define l2 as let l2: ([SomeProtocol]?) -> Void = ..., you allow the closure to accept an argument such as [A(), B()]. Hence this closure, and the closure you define in f2 are incompatible and the compiler cannot convert between the two.
Unfortunately, you cannot add type enforcement to a variable such as l2 as stated here. What you can do is if you know that l2 is going to work on arrays of class A, you could redefine it as follows:
let l2: ([A]?) -> Void = { ... }
Let me try and explain this with a simpler example. Let's say you write a generic function to find the greatest element in an array of comparables:
func greatest<T: Comparable>(array: [T]) -> T {
// return greatest element in the array
}
Now if you try calling that function like so:
let comparables: [Comparable] = [1, "hello"]
print(greatest(array: comparables))
The compiler will complain since there is no way to compare an Int and a String. What you must instead do is follows:
let comparables: [Int] = [1, 5, 2]
print(greatest(array: comparables))
Have nothing on Hamish's answer, he is 100% right. But if you wanna super simple solution without any explanation or code just work, when working with array of generics protocol, use this:
func f1<T: SomeProtocol>(ofType: T.Type, listener: (T?) -> Void) {
}
func f2<Z: SomeProtocol>(ofType: Z.Type, listener: ([SomeProtocol]?) -> Void) {
}
I think best if I start with an example:
class Test<T> {
func test(closure: (T) -> Void) { }
func test(closure: (T) -> T) { }
func test(closure: (T) -> Test<T>) { }
}
Test<Int>().test { a in }
The code above gives the following error:
error: ambiguous use of 'test'
This is because the Swift compiler doesn't know to which one of the three methods is should map the call to. But from the closure body it's quite clear that it returns a Void, so it should pick the first method.
Looks like the Swift compiler cannot determine to which method overload to map the call to based on the closure body. If I explicitly specify the closure signature, then the problem goes away:
Test<Int>().test { (a: Int) -> Void in }
My question is: can I somehow instruct Swift to pick-up the correct overload for short-hand closure expressions like the one in discussion, or will I have to explicitly declare the closure signature?
Actually, it seems that I was pushing the compiler limits too hard (as #Martin R pointed in the comments). The { a in } closure was kinda incomplete, since the compiler had no statements to infer the closure return type from.
This works:
Test<Int>().test { (a: Int) -> Void in () }
Same as the following:
func doNothing() { }
Test<Int>().test { (a: Int) -> Void in doNothing() }
In the above examples the compiler is provided with the minimum amount of information to determine which overload to pick.
In order to avoid code repetition I'm trying to find a way to infer argument type Tor () -> T from a variable of type U.
I don't know if I'm clear enough so this is what I wan't to do:
func f<T>(closure: () -> T) -> String {
return "closure: () -> \(T.self)"
}
func f<T>(value: T) -> String {
return "value: \(T.self)"
}
f("test1") // -> "value: Swift.String"
f({ return "test2" }) // -> "closure: () -> Swift.String"
func foo<U>(bar: U) -> (String, String) {
return ("\(U.self)", f(bar))
}
foo("test3") // (.0 "Swift.String", .1 "value: Swift.String")
foo({ return "test4" }) // (.0 "() -> Swift.String", .1 "value: () -> Swift.String")
I expected foo({ return "test4" }) to call f<T>(closure: () -> T) function instead of f<T>(value: T). Why Swift can't infer that bar: U match with () -> T pattern ?
TL;DR: Type data is lost with foo() / U.
I suspect that you are coming from a C++ background.
This would work in C++ as C++ infers the type based on the call site. Swift is different though, in that the generic parameter is the only source of type data. In your case, you are providing no information about the type U, so the compiler does not know how it could convert it to the closure type. Your value override of f() works because you are not placing any conditions on T, whereas in your closure type override, you are specifying a specific form (a closure). Since all type data is lost by your call through foo that takes generic (think blank) type U, the compiler does not have enough information, and cannot deduce a conversion from (blank) to a closure.
This is the topic of "type reification", and there is a lengthy discussion on this on devforums.apple.com.