I wrote this in scala and it won't compile:
class TestDoubleDef{
def foo(p:List[String]) = {}
def foo(p:List[Int]) = {}
}
the compiler notify:
[error] double definition:
[error] method foo:(List[String])Unit and
[error] method foo:(List[Int])Unit at line 120
[error] have same type after erasure: (List)Unit
I know JVM has no native support for generics so I understand this error.
I could write wrappers for List[String]
and List[Int]
but I'm lazy :)
I'm doubtful but, is there another way expressing List[String]
is not the same type than List[Int]
?
Thanks.
I didn't test this, but why wouldn't an upper bound work?
Does the erasure translation to change from foo( List[Any] s ) twice, to foo( List[String] s ) and foo( List[Int] i ):
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ108
I think I read that in version 2.8, the upper bounds are now encoded that way, instead of always an Any.
To overload on covariant types, use an invariant bound (is there such a syntax in Scala?...ah I think there isn't, but take the following as conceptual addendum to the main solution above):
then I presume the implicit casting is eliminated in the erased version of the code.
UPDATE: The problem is that JVM erases more type information on method signatures than is "necessary". I provided a link. It erases type variables from type constructors, even the concrete bound of those type variables. There is a conceptual distinction, because there is no conceptual non-reified advantage to erasing the function's type bound, as it is known at compile-time and does not vary with any instance of the generic, and it is necessary for callers to not call the function with types that do not conform to the type bound, so how can the JVM enforce the type bound if it is erased? Well one link says the type bound is retained in metadata which compilers are supposed to access. And this explains why using type bounds doesn't enable overloading. It also means that JVM is a wide open security hole since type bounded methods can be called without type bounds (yikes!), so excuse me for assuming the JVM designers wouldn't do such an insecure thing.
At the time I wrote this, I didn't understand that stackoverflow was a system of rating people by quality of answers like some competition over reputation. I thought it was a place to share information. At the time I wrote this, I was comparing reified and non-reified from a conceptual level (comparing many different languages), and so in my mind it didn't make any sense to erase the type bound.
To understand Michael Krämer's solution, it's necessary to recognize that the types of the implicit parameters are unimportant. What is important is that their types are distinct.
The following code works in the same way:
At the bytecode level, both
foo
methods become two-argument methods since JVM bytecode knows nothing of implicit parameters or multiple parameter lists. At the callsite, the Scala compiler selects the appropriatefoo
method to call (and therefore the appropriate dummy object to pass in) by looking at the type of the list being passed in (which isn't erased until later).While it's more verbose, this approach relieves the caller of the burden of supplying the implicit arguments. In fact, it even works if the dummyN objects are private to the
TestDoubleDef
class.There is (at least one) another way, even if it is not too nice and not really type safe:
The implicit manifest paramenter can be used to "reify" the erased type and thus hack around erasure. You can learn a bit more about it in many blog posts,e.g. this one.
What happens is that the manifest param can give you back what T was before erasure. Then a simple dispatch based on T to the various real implementation does the rest.
Probably there is a nicer way to do the pattern matching, but I haven't seen it yet. What people usually do is matching on m.toString, but I think keeping classes is a bit cleaner (even if it's a bit more verbose). Unfortunately the documentation of Manifest is not too detailed, maybe it also has something that could simplify it.
A big disadvantage of it is that it's not really type safe: foo will be happy with any T, if you can't handle it you will have a problem. I guess it could be worked around with some constraints on T, but it would further complicate it.
And of course this whole stuff is also not too nice, I'm not sure if it worth doing it, especially if you are lazy ;-)
Nice trick I've found from http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html by Aaron Novstrup
[...]
As Viktor Klang already says, the generic type will be erased by the compiler. Fortunately, there's a workaround:
Thanks for Michid for the tip!