鉴于以下伴随对象具有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 method。 List[Int](1)
将使编译器使用def apply[T](x1: T)
。
我的问题是为什么第二次实例化会在没有额外“提示”的情况下匹配def apply[T](x1: T, x2: T)
?换句话说,为什么两个参数方法比单个参数方法不是varargs方法更具有特定?
答案 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 weight
与A
一样具体,因为您始终可以使用C
的单个参数调用C
,A
与C
具体相同:您始终可以使用A
参数调用A
,因为C
可以采取任何操作(它的参数类型可以推断为我们想要的任何东西)。 A
的参数被视为C
,因此Seq[A]
在T
中推断为Seq[A]
,并且可以调用它。因此,A
与C
具体相同。如果您将A
更改为A
,就会看到这种情况:它一直在寻找最具体的一个,但这次类型推断无法找到制作{{1}的方法}适用于apply[T <: Int](x: T)
的参数(A
),因为它不是C
的子类型,因此Seq
是最具体的。显然,如果您将Int
更改为A
,类型推断甚至无法执行任何操作,则会发生同样的事情。
这也解释了为什么A
成功调用一个参数版本。两个最终的替代方案是相同的,但apply(x: Int)
的类型参数已绑定到Test[Int](1)
,类型推断不能再将其更改为适合A
的参数。
最后,应用相同的逻辑可以了解为什么Int
正常工作:
C
与Test(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。