Why does the following not compile in Scala:
class A
class B
object X {
def f[Q <: A](q: Q): Q = q
def f[Q <: B](q: Q): Q = q
}
with error message
<console>:16: error: method f is defined twice
conflicting symbols both originated in file '<console>'
def f[Q <: B](q: Q): Q = q
To my understanding, after type erasure, def f[Q <: A](q: Q): Q
should be replaced with its upper bound: def f(q: A): Any
and the second overloaded f
correspondingly. So they should be distinguishable after type erasure.
So why does Scala complain anyway?
Re-posting comment as an answer to improve visibility.
I found this old post about what seems to be the same issue: http://www.scala-lang.org/old/node/4625.html
It seems to be a known issue with the Scala compiler, having to do more with the fact that it would be difficult to support this feature without breaking other (Scala-only) features and guarantees of the compiler. The post also shows few workarounds.
It would be very interesting if any compiler guru here on SO were able to shed some light on whether Dotty - or should I say Skala? ;) - will plan on fixing it.
Just to complement @chengpohi answer, you can actually implement static dispatching (overloading is a particular case) with type-classes:
trait A
trait B
implicit class RichA[Q <: A](q: Q){ def f = q }
implicit class RichB[Q <: B](q: Q){ def f = q }
scala> (new A{}).f
res0: A = $anon$1@39c1fe0b
scala> (new B{}).f
res1: B = $anon$1@20011bf
The reason why it doesn't work naturally is only that Scala has to mimic Java's overloading (with its erasure) to keep code compatible with external Java-code and Scala's internal features and guarantees. Overloading in your case (but not always) is basically a static call, so it can be processed in compile-time, but JVM's invokestatic
does dispatching in runtime unfortunately:
Before performing the method invokation, the class and the method identified by are resolved. See Chapter 9 for a description of how methods are resolved.
invokestatic looks at the descriptor given in , and determines how many arguments the method takes (this may be zero). It pops these arguments off the operand stack. Then it searches the list of static methods defined by the class, locating the method methodname with a descriptor descriptor.
So, regardless that it knows about Q <: A
restriction - it doesn't know about formal type of Q
in the runtime, so some cases like that one pointed by @chengpohi seem to not possible to detect or resolve (actually they could do it based on the info from linearization - the only disadvantage is the involvement of runtime-type into dispatching).
Haskell, for instance, determines the right method in compile-time (as far as I know), so type-classes are able to compensate for the lack of truly static dispatching by deciding the right method to call in compile-time.
P.S. Note that in Haskell overloading is used for dynamic dispatching (pattern matching) and classes for a static one, so it's basically vise-versa in comparison to Java.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With