【问题标题】:Swift inferred closure parameter puzzleSwift 推断闭包参数之谜
【发布时间】:2014-09-06 06:44:07
【问题描述】:

作为对 Ruby 的敬意,我一直在玩 Int 的扩展,它允许我编写如下有用的代码:

3.times { println("I keep holding on") }

这很好用,这是扩展:

extension Int {
    func times(fn: () -> ()) {
        for i in 1...self {
            fn()
        }
    }
}

现在,我想将迭代次数传递给闭包,所以我在扩展中添加了 2nd times() 函数:

extension Int {
    func times(fn: (iteration: Int) -> ()) {
        for i in 1...self {
            fn(iteration: i)
        }
    }
}

可以这样调用:

5.times { (i: Int) -> () in println("Year \(i)") }

现在,根据 Swift 文档,

总是可以推断出参数类型和返回类型 将闭包作为内联闭包表达式传递给函数时。 因此,您永远不需要完整地编写内联闭包 闭包用作函数参数时的形式。

听起来不错,因为这样我可以省略参数和返回类型,即(i: Int) -> (),而只需使用以下语法:

5.times { i in println("Year \(i)") }

但这会导致以下错误:Error: Ambiguous use of 'times'

这种调用我的times() 函数的方式真的对编译器有歧义吗?

【问题讨论】:

  • 请注意,您可以写5.times { (i: Int) in println("Year \(i)") },这样可以避免歧义并更接近于干净的 Ruby 外观。

标签: ios ruby swift


【解决方案1】:

这是模棱两可的。如果闭包的参数类型未知,则.times() 方法都可以与给定的闭包表达式一起使用。

如果你只写{ i in println("Year \(i)") },它只是一个闭包,它接受一个任意类型的参数。好吧,Swift 中的每一个函数类型都可以看作是一个参数:

  • 你认为的零参数函数实际上接受一个类型为()(又名Void)的参数,其值为(),这就是该类型写为() -> something 的原因
  • 您所认为的多参数函数实际上接受一个元组类型的参数,即所有“多参数”的元组,这就是该类型被写为(foo, bar) -> something 的原因。

因此,基本上,您的闭包表达式,无需指定 i 的类型,就可以推断为 Swift 中返回 Void 的任何函数类型。两个.times() 方法所采用的函数都与此匹配——对于第一个.times() 方法,它被推断为() -> () 类型的函数,即i 的类型为();对于第二个.times() 方法,它被推断为Int -> () 类型的函数,即i 具有Int 类型。

【讨论】:

    【解决方案2】:

    看起来歧义源于两个具有相同名称的扩展方法。如果您注释您的第一个版本的 times 函数,它可以正常工作 - 如果您注释第二个版本,令人惊讶的是,您确实不会从编译器中收到错误。

    我认为没有歧义,因为这两个函数具有不同的签名 - () -> ()(iteration: Int) -> () 不同。我认为这是编译器中的一个错误,特别是类型推断失败。

    使调用显式而不是它工作正常

    5.times { (i: Int) -> () in println("Year \(i)") }
    

    如果注释了第一个带有无参数闭包的版本,则上面的行编译正确,如果注释了第二个重载,编译将按预期失败。

    为了证明编译器有问题,这似乎可行,而我希望出现编译错误:

    extension Int {
        func times(fn: () -> ()) {
            for i in 1...self {
                fn()
            }
        }
    }
    
    5.times { i in println("Year \(i)") }
    

    这是 Playground 控制台的输出:

    Year ()
    Year ()
    Year ()
    Year ()
    Year ()
    

    【讨论】:

    • 您发现注释掉 2nd times() 函数不会导致编译器错误。这可能意味着参数i 与一个空元组匹配,让编译器对引用的是哪个times() 函数感到困惑。
    • 是的,我就是这么想的——在我看来,这无疑是由不正确的类型推断引起的编译器错误。
    • "我认为没有歧义,因为这两个函数具有不同的签名 - () -> () 与 (iteration: Int) -> () 不同。我认为这是一个错误编译器,特别是类型推断失败。”没有错误。有两个不同的签名,都匹配给定的闭包。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-07
    • 2020-08-15
    • 2020-04-29
    相关资源
    最近更新 更多