Why does Scala evaluate the argument for a call-by-name parameter if the method is infix and right-associative?
Asked Answered
N

2

11

As I understood call-by-name parameters of a method, the corresponding argument expression will not be evaluated when passing it to the method, but only when (and if) the value of the parameter is used in the method body.

In the following example, however, this is only true in the first two method calls, but not in the third one, although it should be a merely syntactical variation of the second case!?

Why is the argument expression evaluated in the third method call?

(I tested this code using 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??")

Edit of 2020: Note that Scala 2.13 no longer shows this irritating behavior: val node2 = ... no longer prints anything.

Node answered 11/11, 2015 at 17:42 Comment(0)
C
3

By-name arguments are evaluated whenever they are mentioned. The spec says that right-associative operator method calls are evaluated like this:

a op_: b

desugars to:

{ val someFreshName = a; b.op_:(someFreshName) }
//                   ↑↑↑
// Eval happens here ↑↑↑
Couchant answered 11/11, 2015 at 22:52 Comment(0)
H
10

It's a bug. An old one, at that.

See SI-1980 and PR #2852.

The linked pull request added a compiler warning when using the -Xlint flag:

<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
             ^
Harwell answered 11/11, 2015 at 17:52 Comment(4)
Thanks, m-z, for the fast and correct answer. Obviously, it does not make me happy, but that is not your fault ;-)Node
I wouldn't call that a bug. It's what the spec says, and what the spec always has said for as long as right-associative infix operator calls have been in the language, as far as I know. Usually people complain when the compiler doesn't follow the spec, now it does, and they are still not happy :-D (I understand and wholeheartedly agree that the specified behavior makes by-name arguments totally useless for right-associative infix operator calls, but a bug is when the spec disagrees with the implementation, not when you and I disagree with the spec!)Magallanes
@JörgWMittag Fair enough. The discussion in SI-1980 points out that 6.12.3 is somewhat contradicted by 4.6.2 in the spec. If it's not considered a bug, then it's beyond me why the issue is still open and labelled as such.Harwell
@m-z: It's basically an unresolvable tension between trying to make evaluation happen mostly left-to-right and lazy-evaluation. In this particular case, you can't have it both ways. You, the OP, and I would have made a different choice, but it is a valid choice nonetheless.Magallanes
C
3

By-name arguments are evaluated whenever they are mentioned. The spec says that right-associative operator method calls are evaluated like this:

a op_: b

desugars to:

{ val someFreshName = a; b.op_:(someFreshName) }
//                   ↑↑↑
// Eval happens here ↑↑↑
Couchant answered 11/11, 2015 at 22:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.