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

时间:2017-11-18 13:29:46

标签: swift closures

考虑以下功能:

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参数来解决此问题:

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

真正令人生畏的是,我可以简单地转发init@autoclosure参数并且它有效。

我在这里遗漏了一些东西,或者是否因为Swift中的设计而无法为@autoclosure参数提供闭包参数?

1 个答案:

答案 0 :(得分:1)

Swift希望您传递一个导致SELECT l.id_member, m.usertitle FROM smf_user_points_log l INNER JOIN smf_members m ON m.id_member=l.id_member WHERE l.id_school_year in (SELECT id_school_year FROM smf_settings) AND l.points >= 50 AND m.date_registered < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY)) Int的表达式,Swift会将该表达式包装在whatever(foo:)类型的闭包中。

() -> Int

当你这样称呼时:

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

您传递的表达式导致func whatever(foo: {5}) 而不是() -> Int Swift期望。这就是为什么它建议你添加Int并调用该闭包来获得一个返回()的表达式:

Int

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

您可以在Playground中运行此功能来验证这一点:

let x = foo()

输出:

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后重载并调用autoclosure版本:

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 }())