斯卡拉双重定义(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的答案不适用于价值类。
打死这个死马更多…
对我来说,一个更清晰的黑客就是使用一个独特的虚拟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边界没有任何意义。