Scala double definition (2 methods have the same t

2020-01-26 02:53发布

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.

11条回答
Summer. ? 凉城
2楼-- · 2020-01-26 03:33

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.

查看更多
太酷不给撩
3楼-- · 2020-01-26 03:37

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: ClassManifest](p: List[Int]) { println("Ints") }
def foo[X: ClassManifest, Y: ClassManifest](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)}
查看更多
太酷不给撩
4楼-- · 2020-01-26 03:38

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) = ()
}
查看更多
孤傲高冷的网名
5楼-- · 2020-01-26 03:41

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.

查看更多
一纸荒年 Trace。
6楼-- · 2020-01-26 03:46

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.

查看更多
贪生不怕死
7楼-- · 2020-01-26 03:46

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.

查看更多
登录 后发表回答