2010-08-06 72 views
14

我知道类型擦除使他们看起来相等,类型,明智的,在运行时,使:我怎样才能DEF FOO [A]之间区分(XS:A *)和DEF FOO [A,B](XS:(A,B)*)?

class Bar { 
    def foo[A](xs: A*) { xs.foreach(println) } 
    def foo[A, B](xs: (A, B)*) { xs.foreach(x => println(x._1 + " - " + x._2)) } 
} 

提供了以下编译器错误:

<console>:7: error: double definition: 
method foo:[A,B](xs: (A, B)*)Unit and 
method foo:[A](xs: A*)Unit at line 6 
have same type after erasure: (xs: Seq)Unit 
     def foo[A,B](xs: (A, B)*) { xs.foreach(x => println(x._1 + " - " + x._2) 
) } 
      ^

但有一个简单的方法是能写:

bar.foo(1, 2, 3) 
bar.foo(1 -> 2, 3 -> 4) 

,并具有这些调用foo的不同重载版本,而无需显式指定它们:

bar.fooInts(1, 2, 3) 
bar.fooPairs(1 -> 2, 3 -> 4) 
+0

最直接的方法是为每个类型参数使用上下文绑定的ClassManifest:'def foo [A :ClassManifest](xs:A *)...'。我在下面加入了更多评论的答案。 – 2010-08-12 18:14:45

+0

重载时考虑:http://stackoverflow.com/questions/2510108/why-avoid-method-overloading – 2010-08-17 14:59:38

+0

另请参见:http://stackoverflow.com/questions/3307427/scala-double-definition-2-methods-有同样类型的擦除 – retronym 2010-08-17 21:57:32

回答

15

可以,在一个相当周围方式。 Foo是一种类型的类,并且编译器implcitly传递类型的类的一个实例,其中(推断)类型参数A兼容。

trait Foo[X] { 
    def apply(xs: Seq[X]): Unit 
} 

object Foo { 
implicit def FooAny[A]: Foo[A] = new Foo[A] { 
    def apply(xs: Seq[A]) = println("apply(xs: Seq[A])") 
    } 
    implicit def FooTuple2[A, B]: Foo[(A, B)] = new Foo[(A, B)] { 
    def apply(xs: Seq[(A, B)]) = println("apply(xs: Seq[(A, B)])") 
    } 

    def apply[A](xs: A*)(implicit f: Foo[A]) = f(xs) 
} 


Foo(1, 2, 3)  // apply(xs: Seq[A]) 
Foo(1 -> 2, 2 -> 3) // apply(xs: Seq[(A, B)]) 

在第二个电话,都FooAnyFooTuple2可以通过,但是编译器挑选FooTuple2,基于静态方法重载的规则。 FooTuple2被认为更具体,FooAny。如果两个候选人被认为是彼此特定的,则会产生模糊性错误。你也可以选择一个放在超类中,比如scala.LowPriorityImplicits

UPDATE

Riffing关闭DummyImplicit想法,并在斯卡拉用户的后续线程:

trait __[+_] 
object __ { 
implicit object __ extends __[Any] 
} 

object overload { 
def foo(a: Seq[Boolean]) = 0 

def foo[_: __](a: Seq[Int]) = 1 

def foo[_: __ : __](a: Seq[String]) = 2 
} 

import overload._ 
foo(Seq(true)) 
foo(Seq(1)) 
foo(Seq("s")) 

声明一个类型参数化特征__,协在其未命名的类型参数_。它的同伴对象__包含__[Any]一个隐含的实例,我们将需要以后。 foo的第二个和第三个重载包含一个虚拟类型参数,同样未命名。这将被推断为Any。这种类型的参数具有一个或多个上下文的边界,这是脱糖成附加隐含的参数,例如:

def foo[A](a: Seq[Int])(implicit ev$1: __[A]) = 1 

的多个参数列表被连接成在字节码中的单个参数列表,因此双重定义问题规避。

请考虑这是一个机会,了解擦除,上下文范围和隐式搜索,而不是要在实际应用代码的模式!

+0

这看起来不错,但是你正在返回单位......我们限制在那个回报中做什么?我们是否只能从适用的声明中得到类型? – dividebyzero 2015-05-04 17:19:31

3
class Bar { 
    def foo[A](xs: A*) { xs.foreach{ 
     case (a,b) => println(a + " - " + b) 
     case a => println(a)} 
    } 
} 

这将使

bar.foo(1,2) 
bar.foo(1->3,2->4) 

也让

bar.foo(1->2,5) 
4

如果你不介意失去调用foo零个参数(空序列的可能性,如果你喜欢),那么这个技巧可以帮助:

def foo[A](x: A, xs: A*) { x::xs.foreach(println) } 
def foo[A, B](x: (A, B), xs: (A, B)*) { (x::xs.toList).foreach(x => println(x._1 + " - " + x._2)) } 

我不能检查它现在是否工作(即使编译它也不行),但我认为主要想法很容易理解:第一个参数的类型不会被擦除,所以编译器可以根据那。

不幸的是,如果你已经有一个Seq并且你想把它传递给foo,那么它也不是很方便。

+0

这实际上相当不错。顺便说一句,你必须做(x :: xs.toList).foreach(...)。 – 2010-08-06 17:33:32

+0

感谢您的更正,我已修复它。 – 2010-08-18 07:36:01

2

还有一个哈克的方式来得到这个工作:对的方法之一胶水无关的隐含参数:

class Bar { 
    def foo[A](xs: A*) { xs.foreach(println) } 
    def foo[A, B](xs: (A, B)*)(implicit s:String) { xs.foreach(x => println(x._1 + " - " + x._2)) } 
} 

implicit val s = "" 

new Bar().foo(1,2,3,4) 
//--> 1 
//--> 2 
//--> 3 
//--> 4 
new Bar().foo((1,2),(3,4)) 
//--> 1 - 2 
//--> 3 - 4 
8

的情况下,我们只有2个重载,我们可以简化Landei's answer和避免需要通过使用scala.Predef.DummyImplicit来定义我们自己的隐式,这会自动导入到您的每个范围中。

class Bar { 
    def foo[A](xs: A*) { xs.foreach(println) } 
    def foo[A, B](xs: (A, B)*)(implicit s:DummyImplicit){ 
    xs.foreach(x => println(x._1 + " - " + x._2)) 
    } 
} 
+1

实际上,这不仅限于2次重载。它适用于任意数量的重载,只要每个重载具有不同数量的DummyImplicit参数。 – 2010-08-13 21:37:59

+0

以为你可能会感兴趣 - 我在scala用户列表上发布了这个消息:http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic -type-erasure-td2327664.html – 2010-08-17 01:22:26

+0

单线程修复(实际上,只是两个字),这真的很好。想知道Scala应该有什么“适当的”修复来避免需要解决方法。 – bjfletcher 2015-06-10 23:47:35

3

这似乎比retronym's method那么复杂,并且是Ken Bloom's DummyImplicit solution稍微更简洁一些(尽管不那么普遍)版本:

class Bar { 
    def foo[A : ClassManifest](xs: A*) = { xs.foreach(println) } 

    def foo[A : ClassManifest, B : ClassManifest](xs: (A, B)*) = { 
     xs.foreach(x => println(x._1 + " - " + x._2)) 
    } 

    def foo[A : ClassManifest, 
      B : ClassManifest, 
      C : ClassManifest](xs: (A, B, C)*) = { 
     xs.foreach(x => println(x._1 + ", " + x._2 + ", " + x._3)) 
    } 
} 

,如果你有两个重载使用相同的这种技术还可以用于类型参数的数量:

class Bar { 
    def foo[A <: Int](xs: A*) = { 
     println("Ints:"); 
     xs.foreach(println) 
    } 

    def foo[A <: String : ClassManifest](xs: A*) = { 
     println("Strings:"); 
     xs.foreach(println) 
    } 
} 
+0

我不认为它更通用,因为它取决于不同的重载具有不同数量的通用参数的事实。这种技术不适用于'foo(xs:Int *)'和'foo(xs:String *)'之间的歧义。 – 2010-08-12 13:39:19

+0

从更广泛的意义上说,它不限于2次重载。它还涵盖了任何一组2重载,前提是您省略了其中一个重载的上下文绑定。 – 2010-08-12 15:00:51

+0

我想它适用于任何可以传递不同数量的ClassManifests的情况。另一方面,你也可以用不同数量的'DummyImplicits'来做到这一点。 – 2010-08-13 19:56:00

相关问题