69

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.

giampaolo
  • 6,648
  • 5
  • 42
  • 73
Jérôme
  • 941
  • 1
  • 9
  • 12
  • 1
    See also http://stackoverflow.com/questions/3422336/how-can-i-differentiate-between-def-fooaxs-a-and-def-fooa-bxs-a-b – Aaron Novstrup Aug 17 '10 at 22:10
  • 6
    Does anyone know why Scala did not just automatically create different erased names? If you call these methods from outside of Scala which have the work arounds provided in the answers, you will need to know which implicit parameter to pass in order to get the method you want. How is this qualitatively different than needing to manually know which auto-mangled method name to call if calling from outside Scala? The auto-mangled names would be much more efficient and eliminate all this boilerplate! Someday I will get around to asking on scala-debate. – Shelby Moore III Nov 16 '14 at 06:39

11 Answers11

54

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) = ()
}
Jean-Philippe Pellet
  • 56,205
  • 18
  • 161
  • 223
  • 1
    Doesn'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? – expert Apr 30 '13 at 03:57
  • Your two methods both take a `String` as the non-implicit argument. In my examples, the parameter's type is different: `List[String]`, `List[Int]` and `List[Date]`. – Jean-Philippe Pellet Apr 30 '13 at 07:13
  • this actually just kind of solved a whole different problem for me, thanks :) – Erik Kaplun Feb 06 '15 at 18:24
  • Nice! Could you compare this to Landei's solution based on `case class`s? – Daniel Oct 09 '15 at 06:42
  • Like which one do you think is better? – Daniel Oct 09 '15 at 06:42
  • I'd argue this one is more idiomatic. But I'm biased; I proposed this answer. – Jean-Philippe Pellet Oct 09 '15 at 10:04
  • Nice 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. – Jeroen Rosenberg Apr 22 '16 at 14:04
52

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)}
Paweł Prażak
  • 2,819
  • 1
  • 23
  • 39
Landei
  • 52,346
  • 12
  • 89
  • 188
  • 1
    Can you explain more the X:ClassManifest line ? – Mikaël Mayer Aug 08 '13 at 17:40
  • 1
    @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)` and `foo(List l,Classmanifest cx, Classmanifest cy)` which is different from `foo(List l)`. – Landei Aug 08 '13 at 18:29
  • Nice! Could you compare your first solution, i.e. based on `case class`s with the solution provided by Jean-Philippe Pellet? – Daniel Oct 09 '15 at 06:43
  • @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. – Landei Oct 09 '15 at 07:25
  • 5
    `ClassManifest` is a pre-2.10 Scala solution, since then there is `TypeTag`and `ClassTag`. Can you update your solution with plz ? More infos: http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html – Jules Ivanic Apr 27 '16 at 09:40
  • "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. – EECOLOR May 19 '16 at 20:15
13

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.

Community
  • 1
  • 1
Aaron Novstrup
  • 20,477
  • 7
  • 67
  • 107
  • If I mark the dummyN as private and put an implementation in foo methods I get an java.lang.NoSuchMethodError when executing them. – oluies Sep 24 '10 at 19:23
  • Genius! I already had an implicit parameter in place on mine, so I combined them: `(implicit c: MyContext, d: dummy1.type)` – Marcus Downing Aug 13 '11 at 18:54
  • I find this the best answer, and would suggest the original question asker to reconsider. – akauppi May 09 '16 at 11:11
11

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.

Viktor Klang
  • 25,896
  • 7
  • 48
  • 66
  • 2
    Downvote, 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 May 09 '16 at 11:08
8

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!

Michel Krämer
  • 12,563
  • 4
  • 30
  • 32
  • 7
    This 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. – Tom Crockett Jul 22 '10 at 11:29
  • 3
    @peletom: your method (of default parameters) fails to compile with the error "multiple overloaded alternatives of method foo define default arguments." – Ken Bloom Jul 22 '10 at 16:52
6

If I combine Daniels response and Sandor Murakozis response here I get:

@annotation.implicitNotFound(msg = "Type ${T} not supported only Int and String accepted")   
sealed abstract class Acceptable[T]; object Acceptable {
        implicit object IntOk extends Acceptable[Int]
        implicit object StringOk extends Acceptable[String]
}

class TestDoubleDef {
   def foo[A : Acceptable : Manifest](p:List[A]) =  {
        val m = manifest[A]
        if (m equals manifest[String]) {
            println("String")
        } else if (m equals manifest[Int]) {
            println("Int")
        } 
   }
}

I get a typesafe(ish) variant

scala> val a = new TestDoubleDef
a: TestDoubleDef = TestDoubleDef@f3cc05f

scala> a.foo(List(1,2,3))
Int

scala> a.foo(List("test","testa"))
String

scala> a.foo(List(1L,2L,3L))
<console>:21: error: Type Long not supported only Int and String accepted
   a.foo(List(1L,2L,3L))
        ^             

scala> a.foo("test")
<console>:9: error: type mismatch;
 found   : java.lang.String("test")
 required: List[?]
       a.foo("test")
             ^

The logic may also be included in the type class as such (thanks to jsuereth): @annotation.implicitNotFound(msg = "Foo does not support ${T} only Int and String accepted") sealed trait Foo[T] { def apply(list : List[T]) : Unit }

object Foo {
   implicit def stringImpl = new Foo[String] {
      def apply(list : List[String]) = println("String")
   }
   implicit def intImpl = new Foo[Int] {
      def apply(list : List[Int]) =  println("Int")
   }
} 

def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)

Which gives:

scala> @annotation.implicitNotFound(msg = "Foo does not support ${T} only Int and String accepted") 
     | sealed trait Foo[T] { def apply(list : List[T]) : Unit }; object Foo {
     |         implicit def stringImpl = new Foo[String] {
     |           def apply(list : List[String]) = println("String")
     |         }
     |         implicit def intImpl = new Foo[Int] {
     |           def apply(list : List[Int]) =  println("Int")
     |         }
     |       } ; def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
defined trait Foo
defined module Foo
foo: [A](x: List[A])(implicit evidence$1: Foo[A])Unit

scala> foo(1)
<console>:8: error: type mismatch;
 found   : Int(1)
 required: List[?]
       foo(1)
           ^    
scala> foo(List(1,2,3))
Int
scala> foo(List("a","b","c"))
String
scala> foo(List(1.0))
<console>:32: error: Foo does not support Double only Int and String accepted
foo(List(1.0))
        ^

Note that we have to write implicitly[Foo[A]].apply(x) since the compiler thinks that implicitly[Foo[A]](x) means that we call implicitly with parameters.

Community
  • 1
  • 1
oluies
  • 16,976
  • 14
  • 66
  • 113
3

There is (at least one) another way, even if it is not too nice and not really type safe:

import scala.reflect.Manifest

object Reified {

  def foo[T](p:List[T])(implicit m: Manifest[T]) = {

    def stringList(l: List[String]) {
      println("Strings")
    }
    def intList(l: List[Int]) {
      println("Ints")
    }

    val StringClass = classOf[String]
    val IntClass = classOf[Int]

    m.erasure match {
      case StringClass => stringList(p.asInstanceOf[List[String]])
      case IntClass => intList(p.asInstanceOf[List[Int]])
      case _ => error("???")
    }
  }


  def main(args: Array[String]) {
      foo(List("String"))
      foo(List(1, 2, 3))
    }
}

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 ;-)

Sandor Murakozi
  • 4,232
  • 21
  • 27
1

Instead of using manifests you could also use dispatchers objects implicitly imported in a similar manner. I blogged about this before manifests came up: http://michid.wordpress.com/code/implicit-double-dispatch-revisited/

This has the advantage of type safety: the overloaded method will only be callable for types which have dispatchers imported into the current scope.

michid
  • 8,478
  • 3
  • 28
  • 49
0

I tried improving on Aaron Novstrup’s and Leo’s answers to make one set of standard evidence objects importable and more terse.

final object ErasureEvidence {
    class E1 private[ErasureEvidence]()
    class E2 private[ErasureEvidence]()
    implicit final val e1 = new E1
    implicit final val e2 = new E2
}
import ErasureEvidence._

class Baz {
    def foo(xs: String*)(implicit e:E1) = 1
    def foo(xs: Int*)(implicit e:E2) = 2
}

But that will cause the compiler to complain that there are ambiguous choices for the implicit value when foo calls another method which requires an implicit parameter of the same type.

Thus I offer only the following which is more terse in some cases. And this improvement works with value classes (those that extend AnyVal).

final object ErasureEvidence {
   class E1[T] private[ErasureEvidence]()
   class E2[T] private[ErasureEvidence]()
   implicit def e1[T] = new E1[T]
   implicit def e2[T] = new E2[T]
}
import ErasureEvidence._

class Baz {
    def foo(xs: String*)(implicit e:E1[Baz]) = 1
    def foo(xs: Int*)(implicit e:E2[Baz]) = 2
}

If the containing type name is rather long, declare an inner trait to make it more terse.

class Supercalifragilisticexpialidocious[A,B,C,D,E,F,G,H,I,J,K,L,M] {
    private trait E
    def foo(xs: String*)(implicit e:E1[E]) = 1
    def foo(xs: Int*)(implicit e:E2[E]) = 2
}

However, value classes do not allow inner traits, classes, nor objects. Thus also note Aaron Novstrup’s and Leo’s answers do not work with a value classes.

Shelby Moore III
  • 5,808
  • 1
  • 28
  • 34
0

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

Beating this dead horse some more...

It occurred to me that a cleaner hack is to use a unique dummy type for each method with erased types in its signature:

object Baz {
    private object dummy1 { implicit val dummy: dummy1.type = this }
    private object dummy2 { implicit val dummy: dummy2.type = this } 

    def foo(xs: String*)(implicit e: dummy1.type) = 1
    def foo(xs: Int*)(implicit e: dummy2.type) = 2
} 

[...]

Leo
  • 637
  • 5
  • 10
  • 2
    I 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. – Shelby Moore III Nov 16 '14 at 07:25
-3

I didn't test this, but why wouldn't an upper bound work?

def foo[T <: String](s: List[T]) { println("Strings: " + s) }
def foo[T <: Int](i: List[T]) { println("Ints: " + i) }

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):

def foo[T : String](s: List[T]) { println("Strings: " + s) }
def foo[T : String2](s: List[T]) { println("String2s: " + s) }

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.

Community
  • 1
  • 1
Shelby Moore III
  • 5,808
  • 1
  • 28
  • 34
  • 1
    If 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 Feb 16 '11 at 03:58
  • The 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 Feb 16 '11 at 04:14
  • My 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 <: all="" and="" are="" argument="" avoid="" bounds="" but="" casts="" concrete="" erase="" erasure="" for="" in="" is="" just="" list="" more="" need="" not="" of="" only="" parameters.="" propagate="" realizations="" reflection="" reification--="" respects="" runtime="" sane="" should="" some="" string="" than="" the="" to="" type="" unnecessary="" version="" vm=""> – Shelby Moore III Feb 16 '11 at 10:48
  • 1
    This http://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. – Shelby Moore III Feb 16 '11 at 11:29
  • 3
    You 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. – Tony Morris Apr 15 '11 at 12:00
  • Tony 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. – Shelby Moore III Aug 25 '11 at 15:06
  • It'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. – Blaisorblade Dec 22 '11 at 00:59