views:

430

answers:

5

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.

+3  A: 

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.

Jon Quarfoth
A: 

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.

Pete Kirkham
+3  A: 

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.

oxbow_lakes
a's static type is A, and A has only one method called foo (the one that returns Int) so where's the ambiguity?
Laurence Gonsalves
+3  A: 

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.

ptor
+1  A: 

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>
Eastsun