如何区分 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)])
在第二次调用中,FooAny
和 FooTuple2
都可以传递,但编译器会根据静态方法重载的规则选择 FooTuple2
。 FooTuple2
被认为比 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)*)?的主要内容,如果未能解决你的问题,请参考以下文章