Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't the Swift compiler infer this closure's type?

So I was writing code to differentiate multiple versions of my app:

static var jsonURLNL =  {
    if ProcessInfo.processInfo.environment["CONSUMER"] != nil {
        return URL(string: "consumerURL")!
    }
    return URL(string: "professionalURL")!
}()

But I got a compiler error:

Unable to infer complex closure return type; add explicit type to disambiguate

Why can't the Swift compiler know that this will return a URL? I think it is fairly obvious in this case.

My goal with this question is not to give critique on Xcode or Swift, it is to increase my knowledge of how the compiler infers types in Swift.

like image 698
vrwim Avatar asked Mar 01 '17 14:03

vrwim


People also ask

What is inferred type in Swift?

Swift uses type inference extensively, allowing you to omit the type or part of the type of many variables and expressions in your code. For example, instead of writing var x: Int = 0 , you can write var x = 0 , omitting the type completely—the compiler correctly infers that x names a value of type Int .

How does type inference work in Swift?

If you don't specify the type of value you need, Swift uses type inference to work out the appropriate type. Type inference enables a compiler to deduce the type of a particular expression automatically when it compiles your code, simply by examining the values you provide.


2 Answers

The return type of a closure is only inferred automatically if the closure consists of a single expression, for example:

static var jsonURLNL =  { return URL(string: "professionalURL")! }()

or if the type can be inferred from the calling context:

static var jsonURLNL: URL =  {
    if ProcessInfo.processInfo.environment["CONSUMER"] != nil {
        return URL(string: "consumerURL")!
    }
    return URL(string: "professionalURL")!
}()

or

static var jsonURLNL = {
    if ProcessInfo.processInfo.environment["CONSUMER"] != nil {
        return URL(string: "consumerURL")!
    }
    return URL(string: "professionalURL")!
}() as URL

Simplified examples: This single-expression closure compiles:

let cl1 = { return 42 }

but this multi-expression closure doesn't:

let cl2 = { print("Hello"); return 42 }
// error: unable to infer complex closure return type; add explicit type to disambiguate

The following lines compile because the type is inferred from the context:

let cl3 = { print("Hello"); return 42 } as () -> Int

let y1: Int = { print("Hello"); return 42 }()

let y2 = { print("Hello"); return 42 }() as Int

See also the quote from Jordan Rose in this mailing list discussion:

Swift's type inference is currently statement-oriented, so there's no easy way to do [multiple-statement closure] inference. This is at least partly a compilation-time concern: Swift's type system allows many more possible conversions than, say, Haskell or OCaml, so solving the types for an entire multi-statement function is not a trivial problem, possibly not a tractable problem.

and the SR-1570 bug report.

(Both links and the quote are copied from How flatMap API contract transforms Optional input to Non Optional result?).

like image 122
Martin R Avatar answered Oct 06 '22 18:10

Martin R


The inference of the anonymous function's return type might look easy to you, but it turns out that it would be prohibitively difficult to build it into the compiler.

As a result, it is in general not permitted to write a define-and-call initializer without specifying the type as part of the declaration.

But it's not a big problem. All you have to do is specify the type!

static var jsonURLNL : URL = ...

(What I do inside my head is treat the inclusion of the type as part of the syntax for define-and-call initializers. So I always include it. So I never encounter this error message!)


Afterthought: Consider the following:

static var jsonURLNL =  {
    if ProcessInfo.processInfo.environment["CONSUMER"] != nil {
        return "Howdy"
    }
    return URL(string: "professionalURL")!
}()

Do you see the problem? Now nothing can be inferred, even by a human being, because you've accidentally been inconsistent in your return types. But if you write : URL, now the compiler knows what you were supposed to return and knows that "Howdy" is the wrong type.

like image 29
matt Avatar answered Oct 06 '22 19:10

matt