18

给定以下具有重载版本的伴随对象apply

object List {
  def apply[T](): List[T] = new Nil
  def apply[T](x1: T): List[T] = new Cons(x1, new Nil)
  def apply[T](x1: T, x2: T): List[T] = new Cons(x1, new Cons(x2, new Nil))
  def apply[T](elems: T*): List[T] = 
    elems.foldRight(List[T])((elem, l) => new Cons(elem, l))
}

和两个实例化

List(1) // Error - Ambiguity 
List('a', 'b') // Works fine

scalac 抱怨第一次实例化(对重载定义的模糊引用),因为单个参数和 varargs 方法都同样具体

搜索 stackoverflow 我发现可以强制使用单参数方法List[Int](1)将使编译器使用def apply[T](x1: T).

我的问题是为什么第二个实例化匹配def apply[T](x1: T, x2: T)没有额外的“提示”?换句话说,为什么双参数方法比单参数方法没有的可变参数方法更具体?

4

2 回答 2

7

要回答您的问题,我们需要看看 Scala 编译器必须执行重载解析时会发生什么。这在 SLS 6.23.3(对于 Scala 2.9)中有描述。

让我们看一个稍微简单一点的例子:

object Test {
  def apply[T](x1: T) = "one arg"                      // A
  def apply[T](x1: T, x2: T) = "two args"              // B
  def apply[T](elems: T*) = "var-args: " + elems.size  // C
}

看看这三个调用:

Test(1) // fails, ambiguous reference, A and C both match arguments
Test[Int](1) // returns "one arg"
Test(1,2) // returns "two args", not "var-args: 2"

让我们从第一个开始。首先,编译器查看每个参数的形状,这是一种基本上描述参数是值还是函数的类型。在这里,没有难度,1是一个很正常的、无聊的值,它的形状就是类型Nothing

现在它有一个1类型的参数Nothing,并找到适用于它的所有替代方案。它找到其中两个:

  • apply[T](x1: T):它接受一个无界类型的参数,所以它可以接收一个类型的参数Nothing
  • apply[T](elems: T*):它可以应用于任何数量(0包括)相同无界类型的参数,因此它可以接收类型的单个元素Nothing

如果只有一个,它会停在那里并选择那个。

第二步与上述相同,只是这次它使用未定义的预期类型键入每个参数。基本上在这里,它查看剩下的两个替代方案,并确定它们是否适用于1type的参数A <: Int。不走运,他们俩都是。如果你是两个人改变apply[T](x1: T)apply(x1: String)留下另一个人,那么这里将只剩下一个适用的替代方案,它会成功并停止。

然后编译器计算relative weight彼此留下的每个备选方案的 。SLS 指出

备选方案 A 相对于备选方案 B的相对权重是一个从 0 到 2 的数字,定义为

  • 如果 A 与 B 一样具体,则为 1,否则为 0,并且
  • 如果 A 在从定义 B 的类或对象派生的类或对象中定义,则为 1,否则为 0。

在这一点上,必须有一个选项的分数高于所有其他选项,否则会出现歧义错误。我们可以忽略“定义”部分,它们是在同一个地方定义的。

  • A之所以如此具体,是C因为您始终可以C使用 , 的单个参数进行A调用
  • C与类型推断一样具体A:您始终可以使用sinceA的参数调用任何内容(可以将其参数的类型推断为我们想要的任何内容)。的参数被视为一个所以推断为in并且它可以调用它。所以具体如。CACSeq[A]TSeq[A]ACA

如果您更改Aapply[T <: Int](x: T): 它会一直寻找最具体的那个,但这次类型推断找不到A适用于C' 参数 (a Seq) 的方法,因为它不是 , 的子Int类型A最具体的也是如此。显然,如果您更改Aapply(x: Int), 类型推断甚至不能做任何事情,也会发生同样的事情。

这也解释了为什么Test[Int](1)成功调用一个参数版本。最后的两个替代方案是相同的,但是A's 类型参数已绑定到Int并且类型推断不能再将其更改为适合C's 参数。

最后,应用相同的逻辑可以告诉您为什么Test(1,2)可以正常工作:

  • B与 一样具体C:您始终可以C使用B的参数调用,
  • C不如具体:再多的类型推断都无法B将单个类型推断Seq为采用两个参数的方法。

最具体的也是如此apply[T](x1: T, x2: T),没有错误。

基本上,对于 var-arg 和产生歧义的普通方法,它们需要具有相同数量的参数和一种在(至少)最后一个参数上欺骗类型推断的方法:

def apply[T](x1: T)
def apply[T](x: T*)

或者

def apply[T](x1: Int, x2:T)
def apply[T](x1: Int, x: T*)

等等...

编辑:起初我不确定在寻找特异性时是否将重复参数视为 aSeq[A]或 a 。TupleX[...]它绝对不是一个元组,自动元组与这些无关。

于 2013-05-31T20:49:11.033 回答
3

fixed-arity 方法总是比 var-arity 更具体。

f(P1, P2)不适用于(a, b, c, ...),这是你能想到的f(P*)

然而,相反地,为了适用于 N args 的目的而f(P*)采用这种形状f(p1,..,pn)。所以它总是适用并且不像固定参数方法那样具体。

所以这是你f(a,b)f(P*).

对于单参数情况,这取决于您为类型参数选择的内容。

f[A](a: A)确实适用于(a, b, ...)元组并将 A 作为元组。

通过说 A = Int,显然 A 不能被视为元组。

关于 var-arity 以及如何影响特异性的示例混淆:

https://issues.scala-lang.org/browse/SI-4728

object Foo {
  def apply[T](): Int = 1
  def apply[T](x1: T): Int = 2
  def apply[T](x1: T, x2: T): Int = 3
  def apply[T](elems: T*): Int = 4

  // two or more
  def canonically[T](x1: T, x2: T, rest: T*): List[T] = ???
}

object Test extends App {
  Console println Foo(7)
  Console println Foo[Int](7)
  Console println Foo(7,8)
  Console println Foo[Pair[Int,Int]](7,8)
}

您可能想在 stackoverload.com 上发布这个问题,这是重载专家聚集的网站。您的浏览器可能会被重定向到overloading-is-evil.com。

于 2013-05-30T14:48:57.103 回答