【发布时间】:2017-11-18 13:29:46
【问题描述】:
考虑以下函数:
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>。
我可以通过声明一个init 来解决这个问题,该@autoclosure 采用明确的非@autoclosure 参数:
extension Defaults.Setting {
init(key: String, defaultValue: @escaping () -> T) {
self.init(key: key, defaultValue: defaultValue)
}
}
真正令人生畏的是,我可以简单地将init 转发到@autoclosure 参数并且它可以工作。
我在这里遗漏了什么,还是在 Swift 中设计不可能为 @autoclosure 参数提供闭包参数?
【问题讨论】:
-
这似乎是语法冲突。它可能适用于
whatever { 5 }(尾随闭包)或whatever { return 5}。 -
遗憾的是没有。语法似乎就在这里。我的两个版本都出现同样的错误。
-
您可以只说
return Defaults.Setting(key: key.md5(), defaultValue: self.defaultValue())或let defaultValue = self.defaultValue; return Defaults.Setting(key: key.md5(), defaultValue: defaultValue())以避免捕获self。在这两种情况下,闭包调用都包装在自动闭包中,因此在使用默认值之前不会被评估。 AFAIK,唯一可以将闭包转发给自动闭包参数的情况是闭包本身是@autoclosure,例如func foo(_ x: @autoclosure () -> Bool) {}; func bar(_ x: @autoclosure () -> Bool) { foo(x) } -
在这两种情况下,
defaultValue()评估原始闭包只是为了再次包装它,不是吗? -
我刚刚在操场上验证了这一点。你是对的,它不评估但包装它。看来您必须为
@autoclosure参数提供非函数类型。如果您将评论转化为答案,我很乐意接受:)