Encountered a weirdness in Scala (2.8.1) in handling an overloaded method where the first is a no-args one and the second takes a variable number of arguments (0..N). Test code:
class Test {
def method1 = println("method1 invoked with zero args")
def method1(args: Any*) = println("method1 with args " + args)
def method2() = println("method2 invoked with zero args")
def method2(args: Any*) = println("method2 with args " + args)
}
object Test {
def main(args: Array[String]) {
val t = new Test
t.method1
t.method1()
t.method1(1,2,3)
println
t.method2
t.method2()
t.method2(1,2,3)
}
}
By compiling & running it, the output is:
method1 invoked with zero args
method1 with args WrappedArray()
method1 with args WrappedArray(1, 2, 3)
method2 invoked with zero args
method2 invoked with zero args
method2 with args WrappedArray(1, 2, 3)
So if running method1 with parenthesis and zero arguments we get to the varargs method, but in method2‘s case the no-args method is invoked.
What is the explanation or reasoning behind this weird behavior?
Well,
method1()cannot be thedef method =declaration, since it is not possible to call a method without a parameter list passing a parameter list.Note that the error comes from Scala trying to call
apply()on the result ofmethod1, which is anInt.Conversely, one cannot call a vararg method with a parameter list.
Therefore, in the first case, there’s no other possible behavior than what was shown.
In the second example, the first and last cases are non-ambiguous. One can’t call a vararg without a parameter list (as shown), and one can’t call a method without parameters passing a parameter. One can call a method with an empty parameter list without parameters, which was done mostly to make Java APIs more “pretty” — for example,
.toString.The second case — calling the method with an empty parameter list — introduces some ambiguity. Scala then tries to determine if one is more specific than the other.
Let’s take a brief detour here. Why check for more specific methods? Suppose you have this:
This kind of thing is relatively common on Java APIs. If someone called
f("abc"), one naturally expects the compiler to call the second method, not the first, but both are valid. So, how to disambiguate them? Perhaps, sinceStringis a perfect match for what is being passed, one might use that, right? Well, consider, then, this:And I call it with
f(new ArrayList[String])So, what now? Well,AnyRefis a class, whileCollectionis an interface, so we might give precedence to interfaces over classes? And what if I had another method expectingList— that’s also an interface.So, to solve this ambiguity, Scala uses the most specific concept. That is actually a pretty easy concept to apply.
First, Scala verifies is one is as specific as the other. There are four simple rules defined in terms of types and expressions, but the gist of it is that method a is as specific as method b if b can be called with a’s parameters.
In this case,
method2()is as specific asmethod2(args: Any*), becausemethod2(args: Any*)can be called with an empty parameter list. On the other hand,method2(args: Any*)is not as specific asmethod2(), becausemethod2()cannot be called with arguments of typeAny.Next, Scala verifies if one the class that defines one of the methods is a subclass of the class that defines the other. This rule does not apply for this case.
Finally, Scala adds 1 to each method for each of the two criteria above it fits. So
method2()has weight 1, andmethod2(args: Any*)has weight 0. If the weight of one is greater than the other, that method is considered to be the most specific.As we saw,
method2()is the most specific one, so it is chosen.