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

Posted

技术标签:

【中文标题】如何区分 def foo[A](xs: A*) 和 def foo[A, B](xs: (A, B)*)?【英文标题】:How can I differentiate between def foo[A](xs: A*) and def foo[A, B](xs: (A, B)*)? 【发布时间】:2011-03-26 05:45:21 【问题描述】:

我知道类型擦除使它们在运行时在类型方面看起来相等,因此:

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)
【参考方案1】:

你可以,以相当全面的方式。 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

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

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

【讨论】:

这看起来不错,但是你要返回上面的单元...在返回中我们有什么限制?我们是否必须能够仅从 apply 的声明中反转类型?【参考方案2】:

在我们只有 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))
  

【讨论】:

实际上,这不仅限于 2 个重载。它适用于任意数量的重载,只要每个重载具有不同数量的 DummyImplicit 参数。 你可能会感兴趣——我在 scala 用户列表上发布了这个:scala-programming-language.1934581.n4.nabble.com/… 单线修复(其实就两个字),这个真的很甜。想知道 Scala 应该有什么“正确”修复以避免需要解决方法。【参考方案3】:

如果您不介意放弃使用零参数调用 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,它也不是很方便。

【讨论】:

这实际上是一个很好的 hack。顺便说一句,你必须做 (x::xs.toList).foreach(...)。【参考方案4】:
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)

【讨论】:

【参考方案5】:

这似乎没有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)
   

【讨论】:

我不认为它更通用,因为这取决于不同的重载具有不同数量的泛型参数这一事实。这种技术不适用于foo(xs:Int*)foo(xs:String*) 之间的歧义。 它更通用,因为它不限于 2 个重载。它还涵盖了任何一组 2 个重载,前提是您省略了其中一个重载上的上下文绑定。 我猜它适用于您可以传递不同数量的ClassManifests 的任何情况。另一方面,您也可以使用不同数量的 DummyImplicits 来做到这一点。 @Ken 我没想过要使用多个DummyImplicits。好点子。我收回我的一般性断言。 :) 直到我开始思考是什么让ClassManifest 解决方案发挥作用时,我才想到它。【参考方案6】:

还有另一种方法可以让这个工作:在其中一个方法上粘贴一个不相关的隐式参数:

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

【讨论】:

以上是关于如何区分 def foo[A](xs: A*) 和 def foo[A, B](xs: (A, B)*)?的主要内容,如果未能解决你的问题,请参考以下文章

面向对象-封装

python 装饰器

python 装饰器

类的封装

8.xml schema几点问题区分

如何将 XSD 类型导入根架构?