There seems to be a subtlety when using early initializer syntax.
trait Base { def callMe = "callMe" }
trait Proxy { this: Base => def call = s"proxied: $callMe" }
val base1 = new Base { } // non-early init works
val baseFail = new { } with Base // error: trait Base is abstract; cannot be instantiated
val base2 = new { val n=1 } with Base // why does this fix the failure?
val proxy = new { } with Base with Proxy // why doesn't this fail?
Why does the baseFail
line fail, while the other val
s don't?
The error message is also confusing - I'm not trying to instantiate Base
, only mix it in.
When you write
new { } with Base
, there technically aren't any early definitions. According to the SLS 5.1.6, the compiler looks for a pattern like this:Though it doesn't explicitly say what happens when the sequences of definitions is empty, it seems to just remove them, because when you compile
val a = new { val x = 1 } with Base
, you get something like this after the parsing phase:But if you empty the braces, you simply get:
Which is illegal, as is
new Base
.To summarize:
Is an anonymous class, which is allowed :
Simplifies to
new Base
, which is illegal:Is a proper early definition (not empty) which is allowed:
Simplifies to
new Base with Proxy
which is also allowed:new { } with Base { }
also compiles, but it's exactly the same asnew Base { }
, so there's no reason to write it that way.