Swift @autoclosure参数包装提供了显式闭包



考虑以下函数:

func whatever(foo: @autoclosure () -> Int) {
let x = foo()
print(x)
}

当然,我们可以像这样调用它:

whatever(foo: 5)
// prints: 5

但是,提供显式闭包参数会导致编译器抱怨:

whatever(foo: { 5 })
// Error: Function produces expected type 'Int'; did you mean to call it with '()'?

这是有意为之吗?阅读@autoclosure的文档时,我没有找到关于参数是否总是被包装的声明,即使在提供闭包时也是如此。我对@autoclosure的理解是:
采取闭包论证。如果参数不是闭包,但与闭包返回的类型相同,请包装它。
然而,我看到的行为是:无论如何都要包装论点。

一个更详细的例子让我看起来很奇怪:

struct Defaults {
static var dispatcher: Defaults = ...
subscript<T>(setting: Setting<T>) -> T { ... }
struct Setting<T> {
let key: String
let defaultValue: () -> T
init(key: String, defaultValue: @escaping @autoclosure () -> T) {
self.key = key
self.defaultValue = defaultValue
}
}
}
extension Defaults.Setting {
static var nickname: Defaults.Setting<String> {
return Defaults.Setting(key: "__nickname", defaultValue: "Angela Merkel")
}
}
//  Usage:
Defaults.dispatcher[.nickname] = "Emmanuel Macron"

现在假设我想对Setting值的键进行哈希处理:

extension Defaults.Setting {
var withHashedKey: Defaults.Setting<T> {
return Defaults.Setting(key: key.md5(), defaultValue: defaultValue)
// Error: Cannot convert return expression of type 'Defaults.Setting<() -> T>' to return type 'Defaults.Setting<T>'
}
}

澄清一下:defaultValue属于() -> T型。将其提供给init(key: String, defaultValue: () -> T),在我的期望中应该可以正常工作,因为参数和参数具有相同的类型(而参数@autoclosure)。
然而,Swift 似乎包装了提供的闭包,有效地创建了() -> () -> T,从而创建了Setting<() -> T>而不是Setting<T>

我可以通过声明一个采用显式非@autoclosure参数的init来解决此问题:

extension Defaults.Setting {
init(key: String, defaultValue: @escaping () -> T) {
self.init(key: key, defaultValue: defaultValue)
}
}

真正令人生畏的是,我可以简单地转发到init获取@autoclosure参数并且它可以工作。

我在这里错过了一些东西,还是 Swift 的设计无法为@autoclosure参数提供闭包参数?

Swift希望你传递一个表达式,导致Intwhatever(foo:),Swift 会将该表达式包装在类型() -> Int的闭包中。

func whatever(foo: @autoclosure () -> Int) {
let x = foo()
print(x)
}

当你这样称呼它时:

func whatever(foo: {5})

您传递的表达式会导致() -> Int,而不是 Swift 期望Int。 这就是为什么它建议你添加()并调用该闭包来获取返回Int的表达式:

func whatever(foo: {5}())

请注意,由于 Swift 将{5}()包装在闭包中,因此在调用whatever(foo:)之前不会对其进行评估,但实际上调用会延迟到您评估let x = foo()之前。

您可以通过在 Playground 中运行以下命令来验证这一点:

func whatever(foo: @autoclosure () -> Int) {
print("inside whatever")
let x = foo()
print(x)
let y = foo()
print(y)
}
whatever(foo: { print("hi"); return 3 }())

输出:

inside whatever
hi
3
hi
3

如果您希望whatever(foo:也能够采用() -> Int关闭,请重载它并在调用foo后调用自动关闭版本:

func whatever(foo: @autoclosure () -> Int) {
print("autoclosure whatever")
let x = foo()
print(x)
}
func whatever(foo: () -> Int) {
print("closure whatever")
whatever(foo: foo())
}
whatever(foo: { print("two"); return 6 })
whatever(foo: { print("two"); return 6 }())

输出:

closure whatever
autoclosure whatever
two
6
autoclosure whatever
two
6

最新更新