为什么Swift编译器无法推断此闭包的类型?

20

所以我在编写代码来区分我的应用程序的多个版本:

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

但我收到了编译器错误:

无法推断复杂闭包的返回类型;添加显式类型以消除歧义

为什么Swift编译器无法知道这将返回一个URL?在这种情况下,我认为这是相当明显的。

我的目标不是要对Xcode或Swift进行批评,而是增加我了解Swift中编译器如何推断类型的知识。


请查看关于此问题的报告以获取更多详细信息。 - DrummerB
2个回答

24

如果闭包只包含一个表达式,则它的返回类型将自动推断,例如:

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

或者如果调用上下文可以推断出类型:

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

简化示例: 这个单表达式闭包可以编译:

let cl1 = { return 42 }

但是这个多表达式闭包不行:

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

以下代码可以编译通过,因为类型可以从上下文中推断出来:

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

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

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

另请参阅来自Jordan Rose的引用在这个邮件列表讨论中:

Swift的类型推断目前是以语句为导向的,因此没有简单的方法来进行[多语句闭包]推断。这至少在编译时间上是一个问题:Swift的类型系统允许进行比Haskell或OCaml等语言多得多的转换,因此解决整个多语句函数的类型并不是一个轻松的问题,可能不是一个可行的问题。

SR-1570 bug报告

(两个链接和引用都从How flatMap API contract transforms Optional input to Non Optional result?复制而来)。


但它可以检查闭包中的所有返回语句,如果它们都指定了相同的返回类型,那么它将只使用该类型作为该闭包的返回类型?或者我过于简化了吗?我真的不知道Swift的类型推断是如何工作的。 - vrwim
@vrwim:我也不知道细节,但这里有一个例子:您可以使用不同的返回类型重载函数。在像{ if condition { return f() } else { return g() } }这样的闭包中,编译器必须找出是否存在任何具有相同(或兼容)返回类型的fg。根据我从上面的引用中理解的,这并不是微不足道的,可能会使编译过程变慢。 - Martin R

7
匿名函数的返回类型推断可能看起来很容易,但事实证明将其构建到编译器中将非常困难,因此通常不允许在声明中没有指定类型的情况下编写定义并调用初始化程序。
但这不是一个大问题,您只需要指定类型即可!
static var jsonURLNL : URL = ...

我在脑海中的处理方式是将类型的包含视为定义和调用初始化程序语法的一部分,因此我总是包含它。因此,我从未遇到过这个错误消息!


顺便说一下:考虑以下内容:

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

你看到问题了吗?现在即使是人类也无法推断出任何东西,因为你不小心返回类型不一致。但如果你写 : URL,那么编译器就知道你应该返回什么类型,并且知道 "Howdy" 是错误的类型。


问题不在于它是一个“定义和调用初始化器”。static var jsonURLNL = { return URL(string: "professionalURL")! }() 可以编译。 - Martin R
@MartinR 没错,而且 https://bugs.swift.org/browse/SR-1570 明确指出只有当我们进入多行匿名函数时,编译器才会难以处理。但这似乎是太多的信息了。正如我在回答中所说,我总是在声明中包含类型。我的答案回应了 OP 的实际用例,并描述了我思考该用例的方式。 - matt
换句话说,Swift的编译器有问题。只需将机器学习集成到其中,简单易行的修复! - CommaToast
2
@CommaToast 如果你认为这很容易,我相信苹果公司会欢迎你的加入。 - matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接