Search code examples
scalalazy-initializationscala-compiler

Strange behavior of Scala compiler when initializing a class with a lazy argument


How possible that the first is correct Scala code but the second won't even compile?

The one that does compile

object First {
  class ABC(body: => Unit) {
    val a = 1
    val b = 2
    println(body)
  }

  def main(args: Array[String]): Unit = {
    val x = new ABC {
      a + b
    }
  }
}

This one doesn't compile on Scala 2.11 and 2.12

object Second {
  class ABC(body: => Int) {
    val a = 1
    val b = 2
    println(body)
  }

  def main(args: Array[String]): Unit = {
    val x = new ABC {
      a + b
    }
  }
}

Solution

  • It's not strange at all. Let's look at the first example:

    You declare your class ABC to receive a pass by name parameter that returns Unit and you think this snippet:

       val x = new ABC {
          a + b
        }
    

    is passing that body parameter, it isn't.What's really happening is:

    val x = new ABC(()) { a + b }

    If you run that code you will see that println(body) prints () because you're not passing a value for your body parameter, the compiler allows it to compile because as the scaladoc states there is only 1 value of type Unit:

    Unit is a subtype of scala.AnyVal. There is only one value of type Unit, (), and it is not represented by any object in the underlying runtime system. A method with return type Unit is analogous to a Java method which is declared void.

    Since there is only one value the compiler allows you to omit it and it will fill in the gap. This doesn't happen with singleton objects because they don't extend AnyVal. Just has the default value for Int is 0 the default value for Unit is () and because there is only this value available the compiler accepts it.

    From documentation:

    If ee has some value type and the expected type is Unit, ee is converted to the expected type by embedding it in the term { ee; () }.

    Singleton objects don't extend AnyVal so they don't get treated the same.

    When you use syntax like:

    new ABC {
     // Here comes code that gets executed after the constructor code. 
     // Code here can returns Unit by default because a constructor always 
     // returns the type it is constructing. 
    }
    

    You're merely adding things to the constructor body, you are not passing parameters.

    The second example doesn't compile because the compiler cannot infer a default value for body: => Int thus you have to explicitly pass it.

    Conclusion

    Code inside brackets to a constructor is not the same as passing a parameter. It might look the same in same cases, but that's due to "magic".