Scala double definition (2 methods have the same type erasure)
Solution 1
I like Michael Krämer's idea to use implicits, but I think it can be applied more directly:
case class IntList(list: List[Int])
case class StringList(list: List[String])
implicit def il(list: List[Int]) = IntList(list)
implicit def sl(list: List[String]) = StringList(list)
def foo(i: IntList) { println("Int: " + i.list)}
def foo(s: StringList) { println("String: " + s.list)}
I think this is quite readable and straightforward.
[Update]
There is another easy way which seems to work:
def foo(p: List[String]) { println("Strings") }
def foo[X: ClassTag](p: List[Int]) { println("Ints") }
def foo[X: ClassTag, Y: ClassTag](p: List[Double]) { println("Doubles") }
For every version you need an additional type parameter, so this doesn't scale, but I think for three or four versions it's fine.
[Update 2]
For exactly two methods I found another nice trick:
def foo(list: => List[Int]) = { println("Int-List " + list)}
def foo(list: List[String]) = { println("String-List " + list)}
Solution 2
Instead of inventing dummy implicit values, you can use the DummyImplicit
defined in Predef
which seems to be made exactly for that:
class TestMultipleDef {
def foo(p:List[String]) = ()
def foo(p:List[Int])(implicit d: DummyImplicit) = ()
def foo(p:List[java.util.Date])(implicit d1: DummyImplicit, d2: DummyImplicit) = ()
}
Solution 3
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:
class TestDoubleDef {
object dummy1 { implicit val dummy: dummy1.type = this }
object dummy2 { implicit val dummy: dummy2.type = this }
def foo(p:List[String])(implicit d: dummy1.type) = {}
def foo(p:List[Int])(implicit d: dummy2.type) = {}
}
object App extends Application {
val a = new TestDoubleDef()
a.foo(1::2::Nil)
a.foo("a"::"b"::Nil)
}
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 appropriate foo
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.
Solution 4
Due to the wonders of type erasure, the type parameters of your methods' List get erased during compilation, thus reducing both methods to the same signature, which is a compiler error.
Solution 5
As Viktor Klang already says, the generic type will be erased by the compiler. Fortunately, there's a workaround:
class TestDoubleDef{
def foo(p:List[String])(implicit ignore: String) = {}
def foo(p:List[Int])(implicit ignore: Int) = {}
}
object App extends Application {
implicit val x = 0
implicit val y = ""
val a = new A()
a.foo(1::2::Nil)
a.foo("a"::"b"::Nil)
}
Thanks for Michid for the tip!
Jérôme
Updated on June 02, 2022Comments
-
Jérôme almost 2 years
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]
andList[Int]
but I'm lazy :)I'm doubtful but, is there another way expressing
List[String]
is not the same type thanList[Int]
?Thanks.
-
Tom Crockett almost 14 yearsThis seems like a horrible kludge to me, and not worth the effort. A better kludge (still questionably worth it) would be to use parameters with default values to differentiate the two methods.
-
Ken Bloom almost 14 years@peletom: your method (of default parameters) fails to compile with the error "multiple overloaded alternatives of method foo define default arguments."
-
oluies over 13 yearsIf I mark the dummyN as private and put an implementation in foo methods I get an java.lang.NoSuchMethodError when executing them.
-
Shelby Moore III about 13 yearsIf you vote this down, kindly add or send me a comment to explain. I think this works in the latest version? I don't have it installed at the moment to test. In theory the upper bound, should be the type erased to, but I think this was only done in 2.8.
-
Shelby Moore III about 13 yearsThe logic I applied to assume that this must work, is that if the upper bound was only applied with casts inside the function and not to the function's signature, then it would mean the function could be called (by Java) with a type mismatch. Or if you prefer just use the explicit existential type, which afaics is what the upper bound implies, def foo(s: List[_ <: String])
-
Shelby Moore III about 13 yearsMy idea might not work, not because of type erasure per se, but because JVM erases more type information than is necessary for optimum operation of type erasure, thus apparently JVM does not know the difference between a List[T] and a List[T <: String] argument (both are just List). In some respects, type erasure is more sane/efficient than reification-- need only 1 runtime version for all concrete realizations, but to avoid unnecessary casts (i.e. reflection), the VM should propagate and not erase the bounds of the type parameters.
-
Shelby Moore III about 13 yearsThis cakoose.com/wiki/type_erasure_is_not_evil link explains that JVM is discarding too much type information, and thus does not implement optimum type erasure. My suggested idea was based on the assumption that JVM was doing type erasure optimally.
-
Tony Morris about 13 yearsYou can't just make stuff up and expect to be taken seriously and with commentary. Nevertheless, you might want to consider the fact that the JVM erases type variables from type constructors. The correct solution is to use a sum type. Avoid overloading in Scala, always.
-
Marcus Downing over 12 yearsGenius! I already had an implicit parameter in place on mine, so I combined them:
(implicit c: MyContext, d: dummy1.type)
-
Shelby Moore III over 12 yearsTony Morris, you apparently didn't bother to read my comment immediately above yours. 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 distinction, because there is no advantage to erasing the type bound, as it is known at compile-time and does not vary with any instance of the generic.
-
Blaisorblade over 12 yearsIt's obvious that "the JVM erases more type information on method signatures than is "necessary"". C# is implemented without erasure, and there the above problem does not show up; Java could be implemented without erasure as well - the only reason for having erasure in Java was backward compatibility. Finally, erasure is not performed by the JVM but by compilers producing Java bytecode.
-
expert about 11 yearsDoesn't work in my case.
scala: ambiguous reference to overloaded definition, both method apply in class DBObjectExtension of type [A](key: String)(implicit d: DummyImplicit)List[A] and method apply in class DBObjectExtension of type [A](key: String)(implicit d1: DummyImplicit, implicit d2: DummyImplicit)A match argument types (String) and expected result type List[String] val zzzz : List[String] = place("cats") ^
Thoughts? -
Jean-Philippe Pellet about 11 yearsYour two methods both take a
String
as the non-implicit argument. In my examples, the parameter's type is different:List[String]
,List[Int]
andList[Date]
. -
Mikaël Mayer over 10 yearsCan you explain more the X:ClassManifest line ?
-
Landei over 10 years@Mikaël Mayer I parametrize the methods with an unused type X. Because it is really unused, this wouldn't change the signature as seen by the JVM (because of type erasure). However if you use manifests in order to track such types at runtime, you get internally additional arguments for them, so the JVM "sees" something like
foo(List l,Classmanifest cx)
andfoo(List l,Classmanifest cx, Classmanifest cy)
which is different fromfoo(List l)
. -
Shelby Moore III over 9 yearsI downvoted for providing a duplicate to Aaron Novstrup’s answer. I guess you did not check to see he already provided his answer here two years before you did.
-
Erik Kaplun about 9 yearsthis actually just kind of solved a whole different problem for me, thanks :)
-
Daniel over 8 yearsNice! Could you compare this to Landei's solution based on
case class
s? -
Daniel over 8 yearsLike which one do you think is better?
-
Daniel over 8 yearsNice! Could you compare your first solution, i.e. based on
case class
s with the solution provided by Jean-Philippe Pellet? -
Landei over 8 years@Daniel The case class solution requires more typing, but scales much better. Further, it doesn't increase the length of the argument list. In my opinion it is less "hacky". The downside ist that you "pollute" your context with implicit conversions, increase the number of classes, and that you need to "unwrap" your argument inside the method. I don't think that there are major performance differences between both solutions.
-
Jean-Philippe Pellet over 8 yearsI'd argue this one is more idiomatic. But I'm biased; I proposed this answer.
-
Jeroen Rosenberg about 8 yearsNice solution, although it makes testing a bit more difficult. Especially when using mocks, since you practically have an additional parameter to mock and verify against.
-
Jules Ivanic about 8 years
ClassManifest
is a pre-2.10 Scala solution, since then there isTypeTag
andClassTag
. Can you update your solution with plz ? More infos: docs.scala-lang.org/overviews/reflection/… -
akauppi almost 8 yearsDownvote, because the author (at least in the current form of the question) acknowledges they understand the reason - but ask for better ways around it.
-
akauppi almost 8 yearsI find this the best answer, and would suggest the original question asker to reconsider.
-
EECOLOR almost 8 years"For exactly two methods I found another nice trick" - be aware that this changes the semantics. The use of a 'by-name' parameter causes it to be evaluated each time it's used.