对重载定义的模糊引用 - 一对二参数

时间:2013-05-30 06:10:52

标签: scala overloading variadic-functions

鉴于以下伴随对象具有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方法都是equally specific

搜索stackoverflow我发现可以force the single argument methodList[Int](1)将使编译器使用def apply[T](x1: T)

我的问题是为什么第二次实例化会在没有额外“提示”的情况下匹配def apply[T](x1: T, x2: T)?换句话说,为什么两个参数方法比单个参数方法不是varargs方法更具有特定

2 个答案:

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

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

现在它有一个类型为1的{​​{1}}参数,并找到适用于它的所有替代方法。它找到了两个:

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

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

第二步与上面的步骤相同,除了这次它用未定义的期望类型键入每个参数。基本上,它会查看剩下的两个备选方案,并找出它们是否适用于Nothing类型的参数1。没有运气,他们俩都是。 如果您将两个更改A <: Int更改为apply[T](x1: T)并将另一个更改为单独,则此处只剩下一个适用的替代方案,并且会成功并停止。

然后编译器计算每个替代方案的apply(x1: String)。 SLS声明

  

替代A的替代A的相对权重是0到2之间的数字,   

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

此时,必须有一个分数高于其他分数的替代方案,或者存在歧义错误。我们可以忽略“已定义”部分,它们在同一个地方定义。

  • relative weightA一样具体,因为您始终可以使用C的单个参数调用C
  • 由于类型推断,
  • AC具体相同:您始终可以使用A参数调用A,因为C可以采取任何操作(它的参数类型可以推断为我们想要的任何东西)。 A的参数被视为C,因此Seq[A]T中推断为Seq[A],并且可以调用它。因此,AC具体相同。

如果您将A更改为A,就会看到这种情况:它一直在寻找最具体的一个,但这次类型推断无法找到制作{{1}的方法}适用于apply[T <: Int](x: T)的参数(A),因为它不是C的子类型,因此Seq是最具体的。显然,如果您将Int更改为A,类型推断甚至无法执行任何操作,则会发生同样的事情。

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

最后,应用相同的逻辑可以了解为什么Int正常工作:

  • CTest(1,2)具体相同:您始终可以使用B的参数调用C
  • C B一样具体:任何类型的推断都不会使单个C适合采用两个参数。

所以B是最具体的,没有错误。

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

Seq

或者

apply[T](x1: T, x2: T)

等等......

修改:我一开始并不确定重复参数在查找特异性时是被视为def apply[T](x1: T) def apply[T](x: T*) 还是def apply[T](x1: Int, x2:T) def apply[T](x1: Int, x: T*) 。它绝对不是一个元组,自动元组与其中的任何一个都无关。

答案 1 :(得分:3)

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

f(P1, P2)并不适用于(a, b, c, ...),这就是您如何看待f(P*)

相反,f(P*)采用f(p1,..,pn)形状以适用于N args。因此它始终适用,并不像固定方法那样具体。

因此,f(a,b)f(P*)更具体,这是正常的原因。

对于one-arg的情况,它取决于你为类型参数选择的内容。

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

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

关于变异的样本混淆以及如何影响特异性:

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。