在回答
this question时我偶然发现了一个无法解释的行为.
来自(哪里:
val builder = new StringBuilder("foo bar baz ")
(0 until 4) foreach { builder.append("!") }
builder.toString -> res1: String = foo bar baz !
问题似乎很清楚,提供给foreach的函数缺少Int参数,因此StringBuilder.apply被执行了.但这并没有真正解释为什么它附加’!’只有一次.所以我要试验..
我原本期望以下六个陈述是等价的,但最终的字符串有所不同:
(0 until 4) foreach { builder.append("!") } -> res1: String = foo bar baz !
(0 until 4) foreach { builder.append("!")(_) } -> res1: String = foo bar baz !!!!
(0 until 4) foreach { i => builder.append("!")(i) } -> res1: String = foo bar baz !!!!
(0 until 4) foreach { builder.append("!").apply } -> res1: String = foo bar baz !
(0 until 4) foreach { builder.append("!").apply(_) } -> res1: String = foo bar baz !!!!
(0 until 4) foreach { i => builder.append("!").apply(i) } -> res1: String = foo bar baz !!!!
因此,这些陈述显然不相同.有人可以解释一下这个区别吗?
最佳答案 我们给它们贴上标签:
> A – (0到4)foreach {builder.append(“!”).apply}
> B – (0到4)foreach {builder.append(“!”).apply(_)}
> C – (0到4)foreach {i => builder.append(“!”).apply(i)}
乍一看它很令人困惑,因为看起来它们应该完全相同.我们先来看看C.如果我们将它看作一个Function1,那么应该很清楚每次调用都会计算builder.append(“!”).
val C = new Function1[Int, StringBuilder] {
def apply(i: Int): StringBuilder = builder.append("!").apply(i)
}
对于(0到4)中的每个元素,调用C,在每次调用时重新计算builder.append(“!”).
理解这一点的重要一步是B是C的语法糖,而不是A.使用apply(_)中的下划线告诉编译器创建一个新的匿名函数i => builder.append( “!”).申请(I).我们可能不一定期望这样,因为如果eta扩展,builder.append(“!”).apply可以是它自己的一个函数.编译器似乎更喜欢创建一个新的匿名函数,它只是包装builder.append(“!”).apply,而不是eta扩展它.
从SLS 6.23.1 – Placeholder Syntax for Anonymous Functions
An expression e of syntactic category Expr binds an underscore section u, if the following two conditions hold: (1) e properly contains u, and (2) there is no other expression of syntactic category Expr which is properly contained in e and which itself properly contains u.
所以builder.append(“!”).apply(_)正确包含下划线,因此下划线语法可以应用于匿名函数,它变为i => builder.append(“!”).apply(i),like C.
比较这个:
(0 until 4) foreach { builder.append("!").apply _ }
这里,下划线未正确包含在表达式中,因此下划线语法不会立即应用为builder.append(“!”).apply _也可能意味着eta-expansion.在这种情况下,首先是eta扩展,这相当于A.
对于A,它是builder.append(“!”).apply被隐式地eta扩展为一个函数,它只评估builder.append(“!”)一次.例如它是这样的:
val A = new Function1[Int, Char] {
private val a = builder.append("!")
// append is not called on subsequent apply calls
def apply(i: Int): Char = a.apply(i)
}