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" ; } 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" ; ^ /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 ; } 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.