如何区分def foo [A](xs:A *)和def foo [A,B](xs:(A,B)*)?

时间:2010-08-06 09:02:12

标签: scala overloading type-erasure

我知道类型擦除使它们在运行时看起来是相同的,因此:

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)

6 个答案:

答案 0 :(得分:15)

你可以,以相当全面的方式。 Foo是一个类型类,编译器有意地传递类型类的实例,与(推断的)类型参数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都可以传递,但编译器会根据静态方法重载的规则选择FooTuple2FooTuple2被认为更具体FooAny。如果认为两个候选人彼此具体,则会产生歧义错误。您可以通过在超类中放置一个来优先选择其中一个,如scala.LowPriorityImplicits中所做的那样。

<强>更新

重新关注DummyImplicit的想法,以及scala-user上的后续线程:

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

多个参数列表在字节码中连接成一个参数列表,因此避免了双重定义问题。

请将此视为一个了解擦除,上下文边界和隐式搜索的机会,而不是作为在实际代码中应用的模式!

答案 1 :(得分:8)

在我们只有2个重载的情况下,我们可以简化Landei's answer并避免需要定义我们自己的隐式,使用scala.Predef.DummyImplicit自动导入到每个范围。< / p>

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

答案 2 :(得分:4)

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

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,那么它也不是很方便。

答案 3 :(得分: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 :(得分: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)
   }
}

答案 5 :(得分: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