Puzzler 23

Adaptive Reasoning

Scala supports two ways of passing arguments:

  1. By-value, which causes arguments to be evaluated before being passed to the method. This is the default.
  2. By-name, which causes arguments to be evaluated only when referenced inside the method.[1] By-name parameters are prefixed with the => symbol.

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(123) 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)

Possibilities

  1. Prints:
      Puzzled yet?
      puzzler$ Puzzled yet?
    
  2. Prints:
      puzzler$ Puzzled yet?
      puzzler$ Puzzled yet?
    
  3. Prints:
      puzzler$ Puzzled yet?
      Puzzled yet?
    
  4. Fails to compile.

Explanation

The two invocations of Printer.print seem to follow the same code path:

  1. A new instance of Printer is created, with method prompt passed as the constructor argument, prompter.
  2. Parameter prompter is by-name, so it is not evaluated yet and nothing is printed to the console.
  3. Method print is invoked. In the first invocation, no value is provided for prompter so it takes the specified default value, false. Thus, prompter is never invoked and only the value of message is printed. The second call to print, with prompted explicitly set to true, causes prompter to be invoked before message is output.

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.

Discussion

Running this in a REPL with the -Xlint option results in a warning:[5]

Linting Scala code

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?
image images/moralgraphic117px.png Remember that you always need to enclose constructor arguments in parentheses. You can replace parentheses with curly braces only when specifying method arguments.

Footnotes for Chapter 23:

[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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.93.141