在foreach的StringBuilder意外的行为 [英] Unexpected behavior of StringBuilder in foreach

查看:148
本文介绍了在foreach的StringBuilder意外的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在回答这个问题时,我偶然发现了一个我无法解释的行为。 / p>

来自:

  val builder = new StringBuilder(foo bar baz)

(0到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至4)foreach {builder.append(!)(_)} - > res1:String = foo bar baz !!!!
(0至4)foreach {i => builder.append(!)(i)} - > res1:String = foo bar baz !!!!

(0到4)foreach {builder.append(!)。apply} - > res1:String = foo bar baz!
(0到4)foreach {builder.append(!)。apply(_)} - > res1:String = foo bar baz !!!!
(0至4)foreach {i => builder.append(!)。apply(i)} - > res1:String = foo bar baz !!!!

所以这些陈述显然是不相同的。有人可以解释一下这个区别吗?

解决方案


  • A - (0 until 4)foreach {builder.append(!)。apply} $ B - (0 until 4)foreach {builder.append(!) (0至4)。 foreach {i => builder.append(!)。apply(i)}



  • 乍一看, ,因为看来他们应该都是相等的。先看看 C 。如果我们把它看作一个 Function1 ,应该清楚的知道 builder.append(!)是每一个调用进行评估。
    $ b $ pre $ 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-扩展它。 b
    $ b

    SLS 6.23.1 - 匿名函数的占位符语法


    表达式e的语法如果满足以下两个条件,则Expr绑定下划线部分:(1)e正确包含u,(2)没有其他语法类别Expr表达式,它正确地包含在e中,并且它本身恰当地包含u。 / p>

    所以 builder.append(!)。apply(_)包含下划线,所以下划线语法可以应用于匿名函数,并且它变成 i => builder.append(!)。apply(i),例如 C

    <比较这个:

     (0 until 4)foreach {builder.append(!)。apply _} $ 


    $ b

    在这里,下划线没有被正确的包含在表达式中,所以下划线语法不能立即应用为 builder.append(!)。apply _ 也可以表示eta-expansion。在这种情况下,eta-expansion会先到达,这相当于 A



    对于 A ,它是 builder.append(!)。apply 隐式地扩展为一个函数, code> builder.append(!)一次。例如它是

    $ p $ val A = new Function1 [Int,Char] {
    私有val a = builder.append(!)

    // append在随后的应用调用中不会被调用
    def apply(i:Int):Char = a.apply(i )
    }


    While answering this question I stumbled upon a behavior I could not explain.

    Coming from:

    val builder = new StringBuilder("foo bar baz ")
    
    (0 until 4) foreach { builder.append("!") }
    
    builder.toString -> res1: String = foo bar baz !
    

    The issue seemed clear, the function provided to the foreach was missing the Int argument, so StringBuilder.apply got executed. But that does not really explain why it appends the '!' only once. So I got to experimenting..

    I would have expected the following six statements to be equivalent, but the resulting Strings differ:

    (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 !!!!
    

    So the statements are obviously not equivalent. Can somebody explain the difference?

    解决方案

    Let's label them:

    • A - (0 until 4) foreach { builder.append("!").apply }
    • B - (0 until 4) foreach { builder.append("!").apply(_) }
    • C - (0 until 4) foreach { i => builder.append("!").apply(i) }

    At first glance it is confusing, because it appears they should all be equivalent to each other. Let's look at C first. If we look at it as a Function1, it should be clear enough that builder.append("!") is evaluated with each invocation.

    val C = new Function1[Int, StringBuilder] {
        def apply(i: Int): StringBuilder = builder.append("!").apply(i)
    }
    

    For each element in (0 to 4), C is called, which re-evaluates builder.append("!") on each invocation.

    The important step to understanding this is that B is syntactic sugar for C, and not A. Using the underscore in apply(_) tells the compiler to create a new anonymous function i => builder.append("!").apply(i). We might not necessarily expect this because builder.append("!").apply can be a function in it's own right, if eta-expanded. The compiler appears to prefer creating a new anonymous function, that simply wraps builder.append("!").apply, rather than eta-expanding it.

    From the 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.

    So builder.append("!").apply(_) properly contains the underscore, so the underscore syntax can applies for the anonymous function, and it becomes i => builder.append("!").apply(i), like C.

    Compare this to:

    (0 until 4) foreach { builder.append("!").apply _ }
    

    Here, the underscore is not properly contained in the expression, so the underscore syntax does not immediately apply as builder.append("!").apply _ can also mean eta-expansion. In this case, eta-expansion comes first, which will be equivalent to A.

    For A, it is builder.append("!").apply is implicitly eta-expanded to a function, which will only evaluate builder.append("!") once. e.g. it is something like:

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

    这篇关于在foreach的StringBuilder意外的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆