实际上...这个问题的真正答案隐藏在@pamu 的答案中。这个问题的答案有点不重要,需要大量解释。
让我们首先考虑 op 的第一种编译情况,
class A {}
class B extends A {}
object Sample {
def foo(a: Set[A]) {
println("Hi Set[A]")
}
}
Sample.foo(Set(new B()))
但是为什么编译?嗯...答案在于Scala-compiler是一个非常聪明的生物,并且具有type-inference的能力。这意味着如果没有明确提供类型,Scala 会尝试通过查看可用信息来猜测用户可能想要的类型,并将其视为most suitable(最接近的)类型。
现在,在Sample.foo(Set(new B())) 中,Scala 发现foo 将Set[A] 作为参数。它查看提供的参数Set(new B()),它看起来更像Set[B]...但是Scala 编译器的大师“程序员”怎么会犯错误。因此,它会检查是否可以将其实际推断为Set[A]。它成功了。 Scala 编译器很高兴也很自豪,因为它足够聪明,可以理解主人的深刻意图。
Scala 规范section 6.26.1 将此称为Type Instantiation。
为了更清楚地解释它......让我展示当你明确告诉 Scala 类型并且 Scala 不需要使用它的任何推理智能时会发生什么。
// tell scala that it is a set of A
// and we all know that any set of A can contain B
scala> val setA: Set[A] = Set(new B())
setA: scala.collection.immutable.Set[A] = Set(B@17ae2a19)
// Scala is happy with a Set[A]
scala> Sample.foo(setA)
// Hi Set[A]
// tell scala that it is a set of B
// and we all know that any set of B can contain B
scala> val setB: Set[B] = Set(new B())
// setB: scala.collection.immutable.Set[B] = Set(B@17ae2a19)
// But Scala knows that Sample.foo needs a Set[A] and not Set[B]
scala> Sample.foo(setB)
// <console>:20: error: type mismatch;
// found : scala.collection.immutable.Set[B]
// required: Set[A]
// Note: B <: A, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: A`. (SLS 3.2.10)
// Sample.foo(setB)
^
现在我们知道为什么第一个案例适用于 OP。让我们继续第二个案例。
class A {}
class B extends A {}
object Sample {
def foo(a: Set[A]) {
println("Hi Set[A]")
}
def foo(a: String) {
println("Hi A")
}
}
Sample.foo(Set(new B()))
现在...突然Sample.foo(Set(new B())) 无法编译。
原因再次隐藏在 Scala 编译器的“智能”中。 Scala 编译器现在看到了两个Sample.foos。首先想要Set[A],其他想要String。 Scala 应该如何决定程序员想要哪一个。查看已知内容,Scala 发现了一些看起来更像 Set[B] 的东西。
现在我们讨论了类型实例化和推断,一旦 scala 知道期望什么类型,它就可以尝试推断该类型。但是这里 Scala 无法决定期望什么类型,因为它看到了多种选择。因此,在转向类型推断之前,它应该处理重载选择问题,然后才能设置其对推断的期望。
这在 Scala 规范的Overload Resolution (Section 6.26.3) 中进行了讨论。该规范可能看起来有点不透明,所以让我们讨论一下它是如何区分的。
overload resolution其实是由两个问题组成的,
问题 1:: 仅考虑提供的参数,在所有替代方案中,更具体的是 applicable。换句话说,我们查看Applicability 的关于可用替代方案的参数。 Applicability 在Section 6.6 中讨论。 Applicability 首先考虑提供的参数的形状,并在很大程度上依赖于每个类型参数的Compatibility 和Conformance 进行进一步分析。
问题 2 :: 现在,考虑到方法调用的 reference 的类型,我们尝试确定上面选择的替代方案中的 Compatible 是它的类型。
现在,我们开始意识到Compatibility 的重要性,这在section 3.5.4 中有详细讨论。简而言之,两种给定类型(不是函数)的Compatibility 取决于implicit views(两种类型之间的implicit conversions)
如果您通过重载解决规则...您将看到 Scala 编译器将无法解决调用 Sample.foo(Set(new B())) 的多项选择问题。因此无法进行推断,看起来最像 Set[B] 的参数仍被视为 Set[B]。
把它放在very in-accurate 中(只是为了更容易地可视化上面解释的实际问题,不应该以任何方式被视为准确)而是简单的解释 -> 除了@987654361,你们都应该知道@Scala 在那些神奇的隐含 type-class 的帮助下还有另一个神奇的东西叫做 implicit conversions。 Scala 编译器现在看到了两个Sample.foos。首先想要Set[A],其他想要String。但是 Scala 看起来更像是Set[B]。现在它可以尝试将infer 设为Set[A] 或尝试将implicitly convert 设为String。
这两种选择对 Scala 来说都是相当合理的,但现在这个“聪明”的存在对它的高贵大师“程序员”想要什么感到困惑。它不敢在主人的事情上犯任何错误,因此决定将自己的困惑告诉主人并征求主人的意愿。
现在......我们程序员如何帮助解决它的困惑......好吧,我们只是提供更多信息。
例如,
scala> Sample.foo(Set[A](new B()))
// Hi Set[A]
// Or for string
scala> Sample.foo(Set[A](new B()).toString)
// Hi A
// Or,
scala> val setA: Set[A] = Set(new B())
// setA: scala.collection.immutable.Set[A] = Set(B@17ae2a19)
scala> Sample.foo(setA)
// Hi Set[A]
// Or for string
scala> Sample.foo(setA.toString)
// Hi A