How does typecasting / polymorphism work with this nested closure type in Swift?

I know that (Int) -> Void cannot be assigned to (Any) -> Void :

 let intHandler: (Int) -> Void = { i in print(i) } var anyHandler: (Any) -> Void = intHandler <<<< ERROR 

This gives:

error: it is not possible to convert a value of type '(Int) β†’ Void' to the specified type '(Any) β†’ Void'


Question : But I do not know why this work?

 let intResolver: ((Int) -> Void) -> Void = { f in f(5) } let stringResolver: ((String) -> Void) -> Void = { f in f("wth") } var anyResolver: ((Any) -> Void) -> Void = intResolver 

I messed up with the return type and it still works ...:

 let intResolver: ((Int) -> Void) -> String = { f in f(5) return "I want to return some string here." } let stringResolver: ((String) -> Void) -> Void = { f in f("wth") } var anyResolver: ((Any) -> Void) -> Any = intResolver (or stringResolver) 

Sorry if this is set earlier. I could not find such a question yet, maybe I do not know the keyword here. Please enlighten me!

If you want to try: https://iswift.org/playground?wZgwi3&v=3

+5
source share
2 answers

All about variance and closing Swift.

Swift is covariant with the return type of the closure and contradicts its arguments. This makes closures with the same return type or more specific, as well as the same arguments or less specific, to be compatible.

Thus, (Arg1) -> Res1 can be assigned (Arg2) -> Res2 if Res1: Res2 and Arg2: Arg1 .

To express this, slightly modify the first closure:

 import Foundation let nsErrorHandler: (CustomStringConvertible) -> NSError = { _ in return NSError(domain: "", code: 0, userInfo: nil) } var anyHandler: (Int) -> Error = nsErrorHandler 

The above code works because Int corresponds to CustomStringConvertible , and NSError corresponds to Error . Any also work instead of Error , as it is even more general.

Now that we have installed this, let's see what happens in your two blocks of code.

The first block tries to assign a more specific argument closure to a less specific one, and this does not comply with the dispersion rules, so it does not compile.

What about the second block of code? We are in the same scenario as in the first block: closures with one argument.

  • we know that String or Void , more specifically, Any , so we can use it as a return value
  • (Int) -> Void more specific than (Any) -> Void (closure dispersion rules), so we can use it as an argument

Closure variance is taken into account, so intResolver and stringResolver are compatible matches for anyResolver . It sounds a little counter-intuitive, but compilation rules are still followed, and it allows you to assign.

However, if we want to use closure as general arguments, the rejection rules are no longer applied, and this is due to the fact that the generalized Swift data (with some exceptions) are invariant with respect to their type: MyGenericType<B> can't be assigned to MyGenericType<A> even if B: A The exception is standard library structures such as Optional and Array .
+2
source

First, consider why your first example is illegal:

 let intHandler: (Int) -> Void = { i in print(i) } var anyHandler: (Any) -> Void = intHandler // error: Cannot convert value of type '(Int) -> Void' to specified type '(Any) -> Void' 

An (Any) -> Void is a function that can work with any input; a (Int) -> Void is a function that can only handle input Int . Therefore, it follows that we cannot consider the Int -taking function as a function that can deal with anything because it cannot. What if we call anyHandler with String ?

What about the other way? It is legal:

 let anyHandler: (Any) -> Void = { i in print(i) } var intHandler: (Int) -> Void = anyHandler 

Why? Since we can consider a function that deals with anything as a function that can deal with Int , because if it can handle anything, by definition it must deal with Int .

So, we have established that we can consider (Any) -> Void as (Int) -> Void . Take a look at your second example:

 let intResolver: ((Int) -> Void) -> Void = { f in f(5) } var anyResolver: ((Any) -> Void) -> Void = intResolver 

Why can we consider ((Int) -> Void) -> Void as ((Any) -> Void) -> Void ? In other words, why when calling anyResolver can we redirect the argument (Any) -> Void to the parameter (Int) -> Void ? Well, as we have already figured out, we can consider (Any) -> Void as (Int) -> Void , so it is legal.

The same logic applies to your example using ((String) -> Void) -> Void :

 let stringResolver: ((String) -> Void) -> Void = { f in f("wth") } var anyResolver: ((Any) -> Void) -> Void = stringResolver 

When calling anyResolver we can pass it (Any) -> Void , which is then passed to stringResolver , which accepts (String) -> Void . And a function that can deal with anything is also a function that deals with strings, so it is legal.

Playback using return types:

 let intResolver: ((Int) -> Void) -> String = { f in f(5) return "I want to return some string here." } var anyResolver: ((Any) -> Void) -> Any = intResolver 

Because intResolver says it returns String , and anyResolver says it returns Any ; Well, the string is Any , so it is legal.

+1
source

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


All Articles