Scala supports two ways of passing arguments:
By-name parameters are useful in situations where you want to avoid evaluating an argument before a method call, especially if the evaluation is expensive. However, unlike lazy values, by-name parameters are evaluated each time they are referenced inside a method:
def mod(a: => Double) = if (a >= 0) a else -a
scala> mod({ println("evaluating"); -5.2 }) evaluating evaluating res0: Double = 5.2
Another nice feature of Scala is that it allows you to omit parentheses when passing a block. This can give method calls the look and feel of a built-in control structure[2]:
List(1, 2, 3) foreach { e => println(math.abs(e)) }
The following program combines both of these features. What does it do?
class Printer(prompter: => Unit) { def print(message: String, prompted: Boolean = false) { if (prompted) prompter println(message) } }
def prompt() { print("puzzler$ ") }
new Printer { prompt } print (message = "Puzzled yet?") new Printer { prompt } print (message = "Puzzled yet?", prompted = true)
Puzzled yet? puzzler$ Puzzled yet?
puzzler$ Puzzled yet? puzzler$ Puzzled yet?
puzzler$ Puzzled yet? Puzzled yet?
The two invocations of Printer.print seem to follow the same code path:
So the first candidate answer seems to be correct. Unfortunately, when you run the code you'll find the REPL does not agree. The correct answer is number 2!
scala> new Printer { prompt } print (message = "Puzzled yet?") puzzler$ Puzzled yet?
scala> new Printer { prompt } print (message = "Puzzled yet?", prompted = true) puzzler$ Puzzled yet?
The fact that the output is the same for both invocations of print is rather interesting. It turns out the above analysis missed something crucial related to the way in which constructor arguments are passed. Specifically, curly braces can be used in place of parentheses only in the case of method arguments. Constructor arguments, on the other hand, always need to be provided within parentheses.
In short, the following expressions are not equivalent:
new Printer(prompt) new Printer { prompt }
The former creates a new instance of Printer with prompt as the constructor argument. The latter does something very different: it instantiates an anonymous subclass with a no-arg, primary constructor. So, instead of prompt being passed as a constructor argument, it ends up being invoked as part of the constructor of the anonymous subclass.
Nonetheless, Printer has a class parameter (prompter) and it does not look as if a value is being provided when creating the new instances. If prompt is not being passed as a constructor argument, how does the code even compile, seeing as Printer declares a class parameter?
The first step of the explanation can be found in The Scala Language Specification, which specifies that if no explicit constructor arguments are given, an empty argument list, (), is supplied.[3] So the first call to print actually looks like this:
new Printer() { prompt } print (message = "Puzzled yet?")
But this also does not provide a constructor argument, so it is still not clear how the code compiles. This is where another language feature, argument adaptation, comes into play: the compiler attempts to "fix" missing arguments in an argument list by adding the Unit value, (), and seeing if the result type checks.[4] This yields the following expression:
new Printer(()) { prompt } print (message = "Puzzled yet?")
Now you finally see all of the pieces of the puzzle. Since method prompt is part of the class definition (the no-arg primary constructor, to be more precise) it is executed in both cases, as soon as Printer is instantiated. When invoked, the Unit value, (), which is passed as the value of prompter, does nothing. Hence, both calls to print result in identical output.
Running this in a REPL with the -Xlint option results in a warning:[5]
The Scala compiler's -Xlint option enables recommended additional warnings that you can use to flag suspicious language usage, including adapting argument lists.
scala> new Printer { prompt } print (message = "Puzzled yet?") <console>:10: warning: Adapting argument list by inserting (): this is unlikely to be what you want. signature: Printer(prompter: => Unit): Printer given arguments: <none> after adaptation: new Printer((): Unit) new Printer { prompt } print (message = "Puzzled yet?") ^ puzzler$ Puzzled yet?
To achieve the intended behavior, all you have to do is use parentheses to ensure prompt is passed as a constructor argument:
scala> new Printer(prompt) print (message = "Puzzled yet?") Puzzled yet?
scala> new Printer(prompt) print (message = "Puzzled yet?", prompted = true) puzzler$ Puzzled yet?
Remember that you always need to enclose constructor arguments in parentheses. You can replace parentheses with curly braces only when specifying method arguments. |
[1] Odersky, The Scala Language Specification, Section 4.6.1. [Ode14]
[2] Curly braces and parentheses in method calls also feature in Puzzler 35.
[3] Odersky, The Scala Language Specification, Section 5.1.1. [Ode14]
[4] See Puzzler 32 for a more detailed discussion of argument adaptation.
[5] This warning is given by default in Scala 2.11, which deprecates argument adaptation.