为什么Scala在方法是中缀且右关联时会对按名称调用的参数进行评估?

11

据我理解,方法的 call-by-name 参数在传递给方法时不会被求值,只有当(如果)该参数的值在方法体中被使用时才会被求值。

然而,在下面的例子中,尽管它应该仅是第二种情况的语法变化,但这仅适用于前两次方法调用,而不适用于第三次方法调用!?

为什么第三个方法调用中对参数表达式进行了求值?

(我使用Scala 2.11.7测试了这段代码)

class Node(x: => Int)

class Foo {
  def :: (x: =>Int) = new Node(x)  // a right-associative method
  def !! (x: =>Int) = new Node(x)  // a left-associative method
}

// Infix method call will not evaluate a call-by-name parameter:
val node = (new Foo) !! {println(1); 1}
println("Nothing evaluated up to here")

// Right-associative method call will not evaluate a call-by-name parameter:
val node1 = (new Foo).::({println(1); 1})
println("Nothing evaluated up to here")

// Infix and right-associative method call will evaluate a call-by-name parameter - why??
val node2 = {println(1); 1} ::(new Foo)  // prints 1
println("1 has been evaluated now - why??")

2020年的更新:请注意,Scala 2.13不再显示这种令人烦恼的行为:val node2 = ... 不会再打印任何内容。

2个回答

10

这是一个漏洞,而且是一个古老的漏洞。

请参见SI-1980 和 PR #2852

链接的pull请求在使用-Xlint标志时增加了编译器警告:

<console>:13: warning: by-name parameters will be evaluated eagerly when called as a right-associative infix operator. For more details, see SI-1980.
         def :: (x: =>Int) = new Node(x)  // a right-associative method
             ^

谢谢m-z的快速和正确的回答。显然,这并不让我开心,但这不是你的错;-) - Holger Peine
我不会把那称为一个bug。就我所知,自从该语言引入了右结合中缀运算符调用以来,规范就一直是这样写的。通常人们抱怨编译器没有遵循规范,现在它遵循了,他们仍然不满意 :-D(我理解并完全同意,指定行为使按名称传递参数对于右结合中缀运算符调用完全无用,但是当规范与实现不一致时才会出现bug,而你和我与规范不一致时并不是)。 - Jörg W Mittag
@JörgWMittag 好的。SI-1980中的讨论指出,规范中的6.12.3与4.6.2存在一定的矛盾。如果这不被认为是一个错误,那么我不明白为什么问题仍然是开放的并被标记为这样。 - Michael Zajac
@m-z:这基本上是在尝试让评估大部分从左到右进行和惰性评估之间产生无法解决的紧张关系。在这种特定情况下,你不能两者兼得。你、OP和我可能会做出不同的选择,但它仍然是一个有效的选择。 - Jörg W Mittag

3
按名称调用参数是在每次提及时计算的。规范表明,从右向左结合的运算符方法调用如下计算:
a op_: b

转换为:

{ val someFreshName = a; b.op_:(someFreshName) }
//                   ↑↑↑
// Eval happens here ↑↑↑

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接