So I was taking a look at the Scala Array source code, as part of research for a forthcoming blog post (coming soon to a drmaciver.com near you) when I suddenly realised something. Then I suddenly had a WTF moment.

The scala.Array object has a method appy(xs : Int*) : Array[Int], and a method appy(xs : Double*) : Array[Double], and… etc. i.e. it has an overload for each value type and one for the reference types.

Something like this:

 object Overload{ def foo(xs : String*) = "foo"; def foo(xs : Int*) = "bar"; } 

Um.

 /home/david/Foo.scala:3: error: double definition: method foo:(Int*)java.lang.String and method foo:(String*)java.lang.String at line 2 have same type after erasure: (Seq)java.lang.String def foo(xs : Int*) = "bar"; ^ 

See, Scala varargs use Seq, so all varargs erase to the same thing.

So how the hell is scala.Array working? Is it compiler magic?

Well, sortof compiler magic. It’s a compiler magic you too can use. Lets change the above code slightly:

 object Overload{ def foo(xs : String*) = "foo"; def foo(xs : Int*) = 3; } 

This compiles and works fine.

So, what’s going on here?

Well, although this fact is visible only as an implementation detail in most languages, the JVM actually lets you overload based on return type. Two methods are considered identical if they have precisely the same (erased) argument and return types. And although Scala doesn’t let you overload based on return type directly, it seems it will happily accept methods whose arguments erase to the same thing as long as their return types don’t.

This is a bit strange.

This entry was posted in programming and tagged , , on by .

1. Reinier Zwitserloot

I’m fairly sure most JVM languages, including java, won’t understand this. So, cool trick, but can’t use it if you need interop with other JVM languages. Which is a bit of a shame, because the whole point of JVM interop is that it’s not something you program for or think about, you just get it for free.

I’m guessing the original intent was for overloading based on just different return types not to be legal, but then this issue came up, and now its the status quo.

2. Daniel

Overloading based on return type was introduced for exactly one reason: When introducing generics with erasure, they needed “bridge methods”, and these would often conflict with existing “real” ones. To reduce the number of these clashes, they allowed to use overloading based on return type in the JVM, but not in in the compiler for “normal” code.

3. david Post author

No, I don’t think that’s the case.

It’s and covariant return types are the only cases of it actually being used in the Java implementation, but the Java virtual machine spec second edition documents this behaviour and predates both generics and covariant return.