【问题标题】:Anonymous function with no curly braces and no argument labels?没有大括号和参数标签的匿名函数?
【发布时间】:2019-02-05 03:55:58
【问题描述】:

我在another question 上看到了一些代码,它似乎用一些不寻常的语法创建了一个匿名函数(闭包表达式):

let plus: (Int, Int) -> Int = (+)

我理解左边——它声明了一个(Int, Int) -> Int 类型的常量(一个接受两个整数并返回一个整数的函数)。但是(+) 是什么?它如何声明一个没有大括号的函数,以及在没有任何类型的参数标签时它如何引用两个参数?

该函数接受两个参数,将它们相加,然后返回结果。如果我将+ 操作符替换为不同的操作符(比如*),操作就会改变。那么它是{$0 + $1} 的某种简写形式吗?如果是这样,这个速记背后的逻辑是什么?

【问题讨论】:

  • 我什至不确定是否应该在 Stackoverflow 上提出“解释原因”问题。
  • @kelin,大多数问题都是“解释为什么这不起作用”或“解释我如何才能做到这一点”的性质。虽然这可能会解决一个紧迫的需求,但更大的好处是知识被添加到工具箱中——使一个人成为更好的程序员并且可以一次又一次地使用的知识。这就是我问这个问题的原因——因为我在你的代码中看到了一些我认为我可以从中学习并可能在未来使用的东西。

标签: swift lambda anonymous-function shorthand


【解决方案1】:

其实这不是简写。

plus(Int, Int) -> Int 类型的变量。您可以将任何属于此类型(或其任何子类型)的对象分配给它。文字 lambda 闭包当然属于这种类型,但实际上命名函数或方法也可以。这正是这里发生的事情。

它将名为+的运算符方法对象分配给变量。

Closures chapter of the language guide 中隐含地提到了这一点:

运算符方法

实际上还有一个更更短的方法来编写上面的闭包表达式。 Swift 的 String 类型将其大于运算符 (>) 的特定于字符串的实现定义为具有两个 String 类型参数并返回 Bool 类型值的方法。这与sorted(by:) 方法所需的方法类型完全匹配。因此,您可以简单地传入大于运算符,Swift 会推断您想要使用其特定于字符串的实现:

reversedNames = names.sorted(by: >)

因此,代码所做的是将运算符方法+ 分配给变量plus+ 只是分配给变量的函数的名称。不涉及魔术速记。

看到这个你会惊讶吗?

let plus: (Int, Int) -> Int = foo

【讨论】:

  • 感谢 Jörg 的精彩解释。我做了一点 Ruby 编码,所以我很熟悉 + 是一个方法名。我只是没有想到这个看起来很奇怪的(+) 在 Swift 中可能是同一种东西。在 Ruby 中,当您编写 1.+(2) 并得到 3 时,机制就很清楚了——本质上,+ 是 Integer 类的一个实例方法。上面的引用暗示 + 是 Swift 的 Integer 类型的方法,但以下都不起作用(带或不带括号):1.(+)(2)Int.(+)(1, 2)。但是我注意到(+)(1, 2) 确实有效。再次感谢!
  • 是的,Swift 不允许您将函数称为 Int.+Int.(+)。使用. 符号访问的成员必须以字母或_ 开头。运算符方法的特殊之处在于它们使用特殊符号,但编译器希望您在内联3 + 4 中使用它们。 (+)(3, 4) 有效,因为正如我们之前提到的,(+) 在指定类型时确实指定了一个函数(或者可以在这种情况下推断)。
【解决方案2】:

+infix 运算符和 Swift 中的 函数名。在许多类型上定义了许多这样的函数(它是重载的)。

您可以为自己的自定义类型定义+。例如:

struct Foo {
    var value: Int

    static func +(_ lhs: Foo, _ rhs: Foo) -> Foo {
        return Foo(value: lhs.value + rhs.value)
    }
}

var f1 = Foo(value: 5)
var f2 = Foo(value: 3)


let f3 = f1 + f2
print(f3.value) // 8

这行得通:

let plus: (Int, Int) -> Int = (+)

因为+函数的签名已经完全指定,所以Swift能够识别出正确的+函数。

如果我们想将新的+ 函数分配给plus

let plus: (Foo, Foo) -> Foo = (+)

真的和这个没什么不同:

func add(_ a: Int, _ b: Double) -> Double {
    return Double(a) + b
}

let plus: (Int, Double) -> Double = add

print(plus(3, 4.2))  // 7.2

那么为什么是括号呢?为什么指定(+) 而不仅仅是+

+ 在 Swift 中也是一元运算符。

例如,你可以说:

let x = +5

所以只是想说:

let plus: (Int, Int) -> Int = +

使编译器感到困惑,因为它将+ 视为一元前缀运算符,并且它期望+ 后跟5 之类的其他内容。通过用括号括起来,Swift 编译器将停止尝试将 + 解析为一元运算符,并将 is 视为其函数名。即使+ 不是一元前缀运算符,Swift 仍然会期望 + 两侧的值,所以括号告诉 Swift 你没有为函数提供任何输入,而只是想要函数自己。

在没有歧义的情况下,您可以引用不带括号的+ 函数。例如:

var arr = [1, 2, 3]
let sum = arr.reduce(0, +)

【讨论】:

  • 啊……最好的侦探。 :') 总是很高兴阅读您的答案。 :'D 我什至不知道为什么我知道你迟早会来交付时我还要费心回答。 叹息
  • 感谢 vacawama 的精彩解释。 (另请参阅我对 Jörg 回答的评论。)
【解决方案3】:

(+) 本身就是一个运算符方法。您可以像这样声明自己的运算符:

precedencegroup CompositionPrecedence {
    associativity: left
    higherThan: AssignmentPrecedence
}

infix operator •: CompositionPrecedence

func •(a: Int, b: Int) -> Int {
    return a + b
}

用法相同:

var closure: (Int, Int) -> Int = (•)
print("\(closure(1, 2))")

【讨论】:

  • 感谢 Bohdan 的简洁回答。所以真的,我问错了问题! (根本不是匿名函数)
猜你喜欢
  • 2011-10-01
  • 1970-01-01
  • 2012-12-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-24
  • 2012-04-25
  • 2018-03-18
  • 1970-01-01
相关资源
最近更新 更多