Scala 双重定义(2 个方法具有相同的类型擦除)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3307427/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 02:18:26  来源:igfitidea点击:

Scala double definition (2 methods have the same type erasure)

scalacompilationoverloadingtypeclasstype-erasure

提问by Jér?me

I wrote this in scala and it won't compile:

我在 Scala 中写了这个,它不会编译:

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.

我知道 JVM 没有对泛型的本机支持,所以我理解这个错误。

I could write wrappers for List[String]and List[Int]but I'm lazy :)

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

I'm doubtful but, is there another way expressing List[String]is not the same type than List[Int]?

我很怀疑,但是,是否有另一种表达方式List[String]与类型不同List[Int]

Thanks.

谢谢。

采纳答案by Landei

I like Michael Kr?mer's idea to use implicits, but I think it can be applied more directly:

我喜欢 Michael Kr?mer 使用隐式的想法,但我认为它可以更直接地应用:

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]

[更新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)}

回答by Jean-Philippe Pellet

Instead of inventing dummy implicit values, you can use the DummyImplicitdefined in Predefwhich seems to be made exactly for that:

您可以使用似乎正是为此而制作的DummyImplicit定义,Predef而不是发明虚拟的隐式值:

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

回答by Aaron Novstrup

To understand Michael Kr?mer's solution, it's necessary to recognize that the types of the implicit parameters are unimportant. What isimportant is that their types are distinct.

要理解Michael Kr?mer 的解决方案,有必要认识到隐式参数的类型并不重要。什么重要的是,它们的类型是不同的。

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 foomethods 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 foomethod 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).

在字节码级别,这两种foo方法都变成了双参数方法,因为 JVM 字节码对隐式参数或多参数列表一无所知。在调用站点,Scala 编译器foo通过查看传入列表的类型(直到稍后才会删除)来选择适当的方法来调用(因此选择适当的虚拟对象来传入)。

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 TestDoubleDefclass.

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

回答by Viktor Klang

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.

由于类型擦除的神奇之处,你的方法的 List 的类型参数在编译过程中被擦除,从而将两个方法减少到相同的签名,这是一个编译器错误。

回答by Michel Kr?mer

As Viktor Klang already says, the generic type will be erased by the compiler. Fortunately, there's a workaround:

正如 Viktor Klang 已经说过的,泛型类型将被编译器删除。幸运的是,有一个解决方法:

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 Michidfor the tip!

感谢Michid的提示!

回答by oluies

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

如果我在这里结合Daniel回应Sandor Murakozi的回应,我会得到:

@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

我得到一个类型安全(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")
             ^

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 }

逻辑也可以包含在类型类中(感谢jsuereth):@annotation.implicitNotFound(msg = "Foo does not support ${T} only Int and String Accepted") sealtrait Foo[T] { def apply (list : List[T]) : 单位 }

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: 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 implicitlywith parameters.

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

回答by Sandor Murakozi

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.

发生的情况是 manifest 参数可以返回 T 在擦除之前的内容。然后基于 T 到各种实际实现的简单分派完成剩下的工作。

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.

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

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.

它的一个很大的缺点是它不是真正的类型安全:foo 会对任何 T 感到满意,如果你不能处理它,你就会有问题。我想它可以通过对 T 的一些限制来解决,但这会使它进一步复杂化。

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

当然,这整件事也不太好,我不确定是否值得这样做,尤其是如果你很懒的话 ;-)

回答by michid

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/

除了使用清单,您还可以使用以类似方式隐式导入的调度程序对象。我在清单出现之前写了一篇关于这个的博客: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.

这具有类型安全的优点:重载方法仅可用于将调度程序导入到当前作用域的类型。

回答by Leo

Nice trick I've found from http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.htmlby Aaron Novstrup

我从 Aaron Novstrup 的http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html 中发现的好技巧

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:

再打败这匹死马……

我突然想到,一个更简洁的 hack 是为每个在其签名中具有擦除类型的方法使用唯一的虚拟类型:

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
} 

[...]

[...]

回答by Shelby Moore III

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

我尝试改进 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
}

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

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

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

因此,我只提供以下在某些情况下更简洁的内容。这种改进适用于值类(那些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 traitto make it more terse.

如果包含的类型名称很长,请声明一个内部类型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
}

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.

但是,值类不允许内部特征、类或对象。因此还要注意 Aaron Novstrup 和 Leo 的答案不适用于值类。