scala – 在foreach中StringBuilder的意外行为

在回答
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)
}
点赞