2

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
    }
  }
}
tartakynov
  • 2,768
  • 3
  • 26
  • 23

2 Answers2

2

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

pedromss
  • 2,443
  • 18
  • 24
  • `new X` is invoking the constructor of `X`. I will edit my answer regarding `new ABC(null)` – pedromss Aug 28 '17 at 14:24
  • See if it's clearer now. I shouldn't have said `new ABC(null)` – pedromss Aug 28 '17 at 14:25
  • Great fixing the null - the `(())` now makes sense to me. I still think the explanation about "returns the type it is constructing" is misleading. Constructor function body is never expected to return anything, i.e. it is always expected to return Unit, and can therefore return anything, no matter what type you are constructing. – Suma Aug 28 '17 at 14:29
  • This is also not quite correct: "Since there is only one value the compiler allows you to omit it and it will fill in the gap.". You can try a difference between `f(a: => Unit)` and `f(a: => Unit, b => Unit)` - compiler will not "fill in the gaps" in the second case. – Suma Aug 28 '17 at 14:35
  • The same is for " the compiler cannot infer a default value for body" - I do not think it is even attempting to. I think the reason why `new A` is equivalent to `new A(()))` is different (I admit I do not know what the reason is). – Suma Aug 28 '17 at 14:37
  • @Suma with 2 parameters it doesn't work. just like if you have a method with 0 parameters you can use `m1` instead of `m1()` but if your method takes 2 parameters you have to explicitly pass them. Also a constructor is a method that returns an instance of something. It doesn't return `Unit`. Scala allows you to pass an expression that returns `Unit`. You cannot infer that the result of that expression is the return type of the constructor. – pedromss Aug 28 '17 at 14:38
  • I think your description of equivalence of `new A` and `new A(())` is correct I agree it answers the question. However I think the explanation why is it accepted ("fill in the gap with the only one possible value", "cannot infer a default value for body") is misleading. There are other types with only one possible value - singleton (object) types, and you will not see the same behaviour with them: `object A;class ABC(a: A.type) ...`. – Suma Aug 28 '17 at 14:43
  • I am sorry to disagree. Can you provide some reference confirming the notion compiler attempts to find default values for parameters derived from `AnyVal`? – Suma Aug 28 '17 at 14:56
  • The behaviour is somehow specific to `new` invokations. Following will NOT compile: `def f(a: => Unit) = {};f` – Suma Aug 28 '17 at 15:04
  • No need to apologize, healthy argument. Here: http://scala-lang.org/files/archive/spec/2.11/06-expressions.html#value-conversions "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; () }. " - Whenever there is a type Unit expected, the compiler inserts a `()` – pedromss Aug 28 '17 at 15:05
  • Thanks for the great answer! – tartakynov Aug 28 '17 at 15:40
0

You cannot pass a single argument to a constructor in curly braces, because this would be parsed as defining an anonymous class. If you want to do this, you need to enclose the curly braces in normal braces as well, like this:

new ABC({
  a + b
})

As for why does compiler accept new ABC {a + b}, the explanation is a bit intricate and unexpected:

  1. new ABC {...} is equivalent to new ABC() {...}
  2. new ABC() can be parsed as new ABC(()) because of automatic tupling, which is a feature of the parser not mentioned in the specs, see SI-3583 Spec doesn't mention automatic tupling. The same feature casues the following code to compile without an error:

    def f(a: Unit) = {}
    f()
    
    def g(a: (Int, Int)) = {}
    g(0,1)
    

    Note the call produces a warning (even your original example does):

    Adaptation of argument list by inserting () has been deprecated: this is unlikely to be what you want.

    The warning is produced since 2.11, see issue SI-8035 Deprecate automatic () insertion.

Suma
  • 33,181
  • 16
  • 123
  • 191