Scala双重定义(2种方法有相同类型的删除)

时间:2022-02-27 17:04:08

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

我可以编写列表[String]和List[Int]的包装器,但我很懒:)

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

我很怀疑,是否有另一种表达列表[String]的方式与List[Int]不一样?

Thanks.

谢谢。

11 个解决方案

#1


45  

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

我喜欢Michael Kramer使用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)}

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]

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

#2


41  

Instead of inventing dummy implicit values, you can use the DummyImplicit defined in Predef which seems to be made exactly for that:

您可以使用Predef中定义的dummy隐式定义,而不是创建假的隐式值,这似乎是为了实现这一点:

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

#3


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.

由于类型消除的奇妙,您的方法列表的类型参数在编译过程中被删除,因此将这两种方法都减少到相同的签名,这是一个编译器错误。

#4


10  

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.

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

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

在字节码级别,两个foo方法都变成了两个参数的方法,因为JVM字节码对隐式参数或多个参数列表一无所知。在callsite中,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 TestDoubleDef class.

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

#5


8  

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 Michid for the tip!

谢谢你给我小费!

#6


6  

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

如果我把丹尼尔斯的反应和山德或Murakozis的反应结合起来,我得到:

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

该逻辑也可以包含在类型类中(感谢jsuereth): @annotation。隐式(msg = "Foo不支持${T},只支持Int和String accept ")封闭特性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.

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

#7


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.

隐含的manifest paramenter可以用来“具体化”被删除的类型,从而绕过擦除。你可以在很多博客文章中了解到更多。这一个。

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 param可以在删除之前返回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,但我认为保持类更简洁(即使它有点啰嗦)。不幸的是,清单的文档不是太详细,也许它也有一些可以简化它的东西。

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

当然,这整件事也不太好,我不确定是否值得去做,尤其是你懒的时候。

#8


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/

与使用清单不同,您也可以使用类似方式隐式导入的dispatchers对象。在清单出现之前,我在博客上写过: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.

这就有了类型安全的优点:重载的方法只能调用具有当前作用域的dispatchers的类型。

#9


0  

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 foo calls 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).

因此,在某些情况下,我只提供一些更简洁的内容。这个改进与值类(扩展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.

然而,值类不允许内部特性、类或对象。因此,也要注意Aaron Novstrup和Leo的答案与一个值类无关。

#10


-1  

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

我从http://scala-编程语言中找到了一个很好的技巧。

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
} 

[...]

[…]

#11


-2  

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

从foo(List[Any] s)改为foo(List[String] s)和foo(List[Int] i)的擦除转换。

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

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.

我想我在版本2.8中读到过,上界现在是这样编码的,而不是总是一个。

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

在协变类型上重载,使用一个不变约束(Scala中有这样的语法吗?啊,我认为没有,但是把以下作为主要解决方案的概念增编:

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.

更新:问题是,JVM在方法签名上比“必需”更容易删除类型信息。我提供了一个链接。它从类型构造函数中删除类型变量,甚至是那些类型变量的具体绑定。有一个概念上的区别,因为没有概念上的“优势消除函数类型绑定,因为它在编译时是已知的,不随任何通用的实例,并调用者必须不调用这个函数类型不符合类型绑定,那么可以在JVM执行类型绑定,如果是抹去?一个链接说,绑定的类型被保留在编译器应该访问的元数据中。这就解释了为什么使用类型界限不允许重载。这也意味着JVM是一个大的开放安全漏洞,因为类型有界的方法可以被称为没有类型界限(yikes!),所以请原谅我假设JVM设计人员不会做这样的不安全的事情。

At the time I wrote this, I didn't understand that * 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.

在我写这篇文章的时候,我不明白*是一个按质量评分的系统,就像对声誉的竞争一样。我认为这是一个分享信息的地方。在我写这篇文章的时候,我比较了从一个概念性的层次(比较许多不同的语言)的具体化和非具体化,所以在我的头脑中,删除类型界限是没有任何意义的。

#1


45  

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

我喜欢Michael Kramer使用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)}

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]

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

#2


41  

Instead of inventing dummy implicit values, you can use the DummyImplicit defined in Predef which seems to be made exactly for that:

您可以使用Predef中定义的dummy隐式定义,而不是创建假的隐式值,这似乎是为了实现这一点:

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

#3


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.

由于类型消除的奇妙,您的方法列表的类型参数在编译过程中被删除,因此将这两种方法都减少到相同的签名,这是一个编译器错误。

#4


10  

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.

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

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

在字节码级别,两个foo方法都变成了两个参数的方法,因为JVM字节码对隐式参数或多个参数列表一无所知。在callsite中,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 TestDoubleDef class.

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

#5


8  

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 Michid for the tip!

谢谢你给我小费!

#6


6  

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

如果我把丹尼尔斯的反应和山德或Murakozis的反应结合起来,我得到:

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

该逻辑也可以包含在类型类中(感谢jsuereth): @annotation。隐式(msg = "Foo不支持${T},只支持Int和String accept ")封闭特性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.

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

#7


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.

隐含的manifest paramenter可以用来“具体化”被删除的类型,从而绕过擦除。你可以在很多博客文章中了解到更多。这一个。

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 param可以在删除之前返回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,但我认为保持类更简洁(即使它有点啰嗦)。不幸的是,清单的文档不是太详细,也许它也有一些可以简化它的东西。

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

当然,这整件事也不太好,我不确定是否值得去做,尤其是你懒的时候。

#8


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/

与使用清单不同,您也可以使用类似方式隐式导入的dispatchers对象。在清单出现之前,我在博客上写过: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.

这就有了类型安全的优点:重载的方法只能调用具有当前作用域的dispatchers的类型。

#9


0  

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 foo calls 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).

因此,在某些情况下,我只提供一些更简洁的内容。这个改进与值类(扩展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.

然而,值类不允许内部特性、类或对象。因此,也要注意Aaron Novstrup和Leo的答案与一个值类无关。

#10


-1  

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

我从http://scala-编程语言中找到了一个很好的技巧。

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
} 

[...]

[…]

#11


-2  

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

从foo(List[Any] s)改为foo(List[String] s)和foo(List[Int] i)的擦除转换。

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

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.

我想我在版本2.8中读到过,上界现在是这样编码的,而不是总是一个。

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

在协变类型上重载,使用一个不变约束(Scala中有这样的语法吗?啊,我认为没有,但是把以下作为主要解决方案的概念增编:

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.

更新:问题是,JVM在方法签名上比“必需”更容易删除类型信息。我提供了一个链接。它从类型构造函数中删除类型变量,甚至是那些类型变量的具体绑定。有一个概念上的区别,因为没有概念上的“优势消除函数类型绑定,因为它在编译时是已知的,不随任何通用的实例,并调用者必须不调用这个函数类型不符合类型绑定,那么可以在JVM执行类型绑定,如果是抹去?一个链接说,绑定的类型被保留在编译器应该访问的元数据中。这就解释了为什么使用类型界限不允许重载。这也意味着JVM是一个大的开放安全漏洞,因为类型有界的方法可以被称为没有类型界限(yikes!),所以请原谅我假设JVM设计人员不会做这样的不安全的事情。

At the time I wrote this, I didn't understand that * 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.

在我写这篇文章的时候,我不明白*是一个按质量评分的系统,就像对声誉的竞争一样。我认为这是一个分享信息的地方。在我写这篇文章的时候,我比较了从一个概念性的层次(比较许多不同的语言)的具体化和非具体化,所以在我的头脑中,删除类型界限是没有任何意义的。