斯卡拉双重定义(2种方法有相同types的删除)

我用scala写这个,不会编译:

class TestDoubleDef{ def foo(p:List[String]) = {} def foo(p:List[Int]) = {} } 

编译器通知:

 [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 

我知道JVM没有原生支持generics,所以我明白这个错误。

我可以为List[String]List[Int]编写包装,但我很懒惰:)

我怀疑,但是,有没有另一种方式expressionList[String]不是比List[Int]相同的types?

谢谢。

我喜欢MichaelKrämer的想法,使用implicits,但我认为它可以更直接地应用:

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

我认为这是非常可读和直接的。

[更新]

还有另一个简单的方法,似乎工作:

 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") } 

对于每一个版本你需要一个额外的types参数,所以这不会扩展,但我认为对于三个或四个版本,它是好的。

[更新2]

对于正好两种方法,我发现了另一个好方法:

 def foo(list: => List[Int]) = { println("Int-List " + list)} def foo(list: List[String]) = { println("String-List " + list)} 

您可以使用在DummyImplicit定义的Predef ,而不是创build虚拟隐式值,这似乎是为了:

 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) = () } 

由于types擦除的奇迹,在编译期间,方法列表的types参数被擦除,从而将两个方法都减less到相同的签名,这是一个编译器错误。

为了理解MichaelKrämer的解决scheme ,有必要认识到隐式参数的types是不重要的。 重要的是它们的types是不同的。

以下代码以相同的方式工作:

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

在字节码级别,两个foo方法都变成双参数方法,因为JVM字节码对隐式参数或多个参数列表一无所知。 在调用的地方,Scala编译器通过查看被传入的列表的types(直到后面没有被擦除),select适当的foo方法来调用(并因此传入适当的虚拟对象)。

虽然它更详细,但这种方法可以减轻调用者提供隐式参数的负担。 实际上,如果dummyN对象对于TestDoubleDef类是私有的,它甚至可以工作。

正如Viktor Klang所说,genericstypes将被编译器擦除。 幸运的是,有一个解决方法:

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

感谢Michid的提示!

如果我把丹尼尔的回应和桑德尔·穆拉科齐的回答结合起来,我会得到:

 @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") } } } 

我得到一个types安全(ish)变体

 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") ^ 

这个逻辑也可以包含在type类中(感谢jsuereth ):@ annotation.implicitNotFound(msg =“Foo不支持$ {T}只有Int和String被接受”)密封特质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) 

这使:

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

注意,我们必须implicitly[Foo[A]].apply(x)地写implicitly[Foo[A]].apply(x)因为编译器认为implicitly[Foo[A]](x)意味着我们用参数implicitly调用。

至less有一种方法,即使它不是太好,也不是很安全:

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

隐式清单参数可用于“擦除”擦除types,从而绕过擦除。 您可以在许多博客文章中了解更多信息,例如这个 。

会发生什么是清单参数可以让你回到T之前擦除。 剩下的就是基于T的简单调度。

可能有一个更好的方式来做模式匹配,但我还没有看到它。 人们通常在m.toString上进行匹配,但是我认为让类更清洁一些(即使它稍微冗长些)。 不幸的是,Manifest的文档不是太详细,也许它也有一些可以简化它的东西。

它的一个很大的缺点是它不是非常安全的:foo对任何T都很满意,如果你处理不了,就会有问题。 我猜这可能会在T上受到一些限制,但是会让它更加复杂。

当然,这整个东西也不是很好,我不知道是否值得这样做,特别是如果你是懒惰的;-)

您也可以使用以类似方式隐式导入的调度程序对象,而不是使用清单。 我之前在博客上发表了博客: http : //michid.wordpress.com/code/implicit-double-dispatch-revisited/

这具有types安全性的优点:重载的方法只能对调度程序导入当前范围的types进行调用。

我试着改进Aaron Novstrup和Leo的答案,使一组标准证据对象可导入和更简洁。

 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 } 

但是,这会导致编译器抱怨,当foo调用另一个需要相同types的隐式参数的方法时,隐式值有不明确的select。

因此,我只提供以下在某些情况下更简洁的内容。 而且这种改进适用于值类( 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 } 

如果包含的types名称很长,则声明一个内部trait以使其更加简洁。

 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 } 

然而,价值类不允许内在的特质,类别和对象。 因此,也注意到Aaron Novstrup和Leo的答案不适用于价值类。

我从http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.htmlfind的好手法Aaron Novstrup

打死这个死马更多…

对我来说,一个更清晰的黑客就是使用一个独特的虚拟types的每个方法擦除types签名:

 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 } 

[…]

我没有testing这个,但为什么不会有上限的工作?

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

擦除翻译是否从foo(List [Any] s)两次更改为foo(List [String] s)和foo(List [Int] i):

http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ108

我想我已经阅读了2.8版本,现在编码的上限是这样的,而不是总是一个任何。

要在协变types上重载,使用一个不变的界限(在Scala中是否有这样的语法?…我认为没有,但是以上面的主要解决scheme的概念性附录):

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

那么我认为隐含的转换在代码的删除版本中被消除了。


更新:问题是JVM擦除方法签名的更多的types信息比“必要”。 我提供了一个链接。 它从types构造函数中擦除typesvariables,甚至是这些typesvariables的具体边界。 有一个概念上的区别,因为在删除函数的types绑定方面没有概念上的非泛化的好处,因为它在编译时是已知的,并且不随generics的任何实例而变化,并且调用者不需要调用该函数的types不符合绑定的types,那么如果JVM被删除,JVM如何强制绑定types呢? 那么一个链接说types绑定保留在编译器应该访问的元数据中。 这就解释了为什么使用types边界不能启用重载。 这也意味着JVM是一个开放的安全漏洞,因为types有界的方法可以被调用而没有types边界(yikes!),所以请原谅JVMdevise者不要做这样不安全的事情。

在我写这篇文章的时候,我不明白,stackoverflow是一个按照质量来评价人的系统。 我认为这是一个分享信息的地方。 在我写这篇文章的时候,我从概念层面(比较了很多不同的语言)比较了物化和非物化,所以在我看来,擦除types边界没有任何意义。