开发者

Why is method overloading not defined for different return types?

In Scala, you can overload a method by having methods that share a common name, but which either have different arities or different parameter types. I was wondering why this wasn't also extended to the return type of a method? Consider the following code:

class C {
  def m: Int = 42
  def m: String = "forty two"
}

val c = new C
val i: Int = C.m
val s: String 开发者_开发知识库= C.m

Is there a reason why this shouldn't work?

Thank you,

Vincent.


Actually, you can make it work by the magic of 'implicit'. As following:

scala> case class Result(i: Int,s: String)

scala> class C {
     |     def m: Result = Result(42,"forty two")
     | }

scala> implicit def res2int(res: Result) = res.i

scala> implicit def res2str(res: Result) = res.s

scala> val c = new C

scala> val i: Int = c.m

i: Int = 42


scala> val s: String = c.m

s: String = forty two

scala>


You can of course have overloading for methods which differ by return type, just not for methods which differ only by return type. For example, this is fine:

def foo(s: String) : String = s + "Hello"
def foo(i: Int) : Int = i + 1

That aside, the answer to your question is evidently that it was a design decision: the return type is part of the method signature as anyone who has experienced an AbstractMethodError can tell you.

Consider however how allowing such overloading might work in tandem with sub-typing:

class A {
  def foo: Int = 1
}
val a: A = //...lookup an A
val b = a.foo

This is perfectly valid code of course and javac would uniquely resolve the method call. But what if I subclass A as follows:

class B extends A {
  def foo: String = "Hello"
}

This causes the original code's resolution of which method is being called to be broken. What should b be? I have logically broken some existing code by subtyping some existing class, even though I have not changed either that code or that class.


The main reason is complexity issues: with a "normal" compiler approach, you go inside-out (from the inner expression to the outer scope), building your binary step by step; if you add return-type-only differentiation, you need to change to a backtracking approach, which greatly increases compile time, compiler complexity (= bugs!).

Also, if you return a subtype or a type that can be automatically converted to the other, which method should you choose? You'd give ambiguity errors for perfectly valid code.

Not worth the trouble.

All in all, you can easily refactor your code to avoid return-type-only overload, for example by adding a dummy parameter of the type you want to return.


I've never used scala, so someone whack me on the head if I'm wrong here, but this is my take.

Say you have two methods whose signatures differ only by return type.

If you're calling that method, how does the compiler (interpreter?) know which method you actually want to be calling?

I'm sure in some situations it might be able to figure it out, but what if, for example, one of your return types is a subclass of the other? It's not always easy.

Java doesn't allow overloading of return types, and since scala is built on the java JVM, it's probably just a java limitation.

(Edit) Note that Covariant returns are a different issue. When overriding a method, you can choose to return a subclass of the class you're supposed to be returning, but cannot choose an unrelated class to return.


In order to differentiate between different function with the same name and argument types, but different return types, some syntax is required, or analysis of the site of an expression.

Scala is an expression oriented language (every statement is an expression). Generally expression oriented languages prefer to have the semantics of expressions to be dependent only on the scope evaluation occurs in, not what happens to the result, so for the expression foo() in i_take_an_int( foo() ) and i_take_any_type ( foo()) and foo() as a statement all call the same version of foo().

There's also the issue that adding overloading by return type to a language with type inference will make the code completely incomprehensible - you'd have to keep an incredible amount of the system in mind in order to predict what will happen when code gets executed.


All answers that say the JVM does not allow this are straight up wrong. You can overload based on return type. Surprisingly, the JVM does allow this; it's the compilers for languages that run on the JVM that don't allow this. But there are ways to get around compiler limitations in Scala.

For example, consider the following snippet of code:

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

This will throw a compiler error (Because varargs, indicated by the * after the argument type, type erase to Seq):

Error:(217, 11) double definition:
def foo(xs: String*): String at line 216 and
def foo(xs: Any*): String at line 217
have same type after erasure: (xs: Seq)String
      def foo(xs: Any*) = "bar";

However, if you change value of the second foo to 3 instead of bar (that way changing the return type from String to Int) as follows:

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

... you won't get a compiler error.

So you can do something like this:

val x: String = Overload.foo()
val y: Int = Overload.foo()

println(x)
println(y)

And it will print out:

3
foo

However, the caveat to this method is having to add varargs as the last (or only) argument for the overloaded functions, each with with their own distinct type.

Source: http://www.drmaciver.com/2008/08/a-curious-fact-about-overloading-in-scala/

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜