【问题标题】:Swift @autoclosure parameter wraps provided explicit closureSwift @autoclosure 参数包装提供了显式闭包
【发布时间】: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 的类型是 () -&gt; T。将它提供给init(key: String, defaultValue: () -&gt; T),在我的期望中应该可以工作,因为参数和参数具有相同的类型(而参数是@autoclosure)。
但是,Swift 似乎包装了提供的闭包,有效地创建了() -&gt; () -&gt; T ,这会创建 Setting&lt;() -&gt; T&gt; 而不是 Setting&lt;T&gt;

我可以通过声明一个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 () -&gt; Bool) {}; func bar(_ x: @autoclosure () -&gt; Bool) { foo(x) }
  • 在这两种情况下,defaultValue() 评估原始闭包只是为了再次包装它,不是吗?
  • 我刚刚在操场上验证了这一点。你是对的,它不评估但包装它。看来您必须为 @autoclosure 参数提供非函数类型。如果您将评论转化为答案,我很乐意接受:)

标签: swift closures


【解决方案1】:

Swift 希望您将一个导致Int 的表达式传递给whatever(foo:),Swift 会将该表达式包装在() -&gt; Int 类型的闭包中。

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

当你这样称呼它时:

func whatever(foo: {5})

您传递的表达式导致 () -&gt; Int 而不是 Int Swift 所期望的。这就是为什么它建议您添加 () 并调用该闭包以获取返回 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: 也能够采用() -&gt; 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

【讨论】:

  • Swift 希望你传递一个表达式,结果是 Int to whatever(foo:) - 是的。我现在明白了。但是,最好在某处记录这个问题。不管怎样,我今天学到了一些东西。
猜你喜欢
  • 1970-01-01
  • 2017-06-30
  • 1970-01-01
  • 1970-01-01
  • 2016-04-20
  • 1970-01-01
  • 1970-01-01
  • 2017-01-29
  • 2020-02-14
相关资源
最近更新 更多