为什么以看似无关的方式重载方法时,scala 无法编译?

Why does scala fail to compile when method is overloaded in a seemingly unrelated way?

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()))

以上代码与 scala 一起运行得很愉快。但是,当我取消注释 foo(a: String) 时,代码编译失败:

test.scala:13: error: overloaded method value foo with alternatives:
  (a: String)Unit <and>
  (a: Set[this.A])Unit
 cannot be applied to (scala.collection.immutable.Set[this.B])
Sample.foo(Set(new B()))
       ^
one error found

foo(a: String) 似乎应该与尝试用 Set[B] 调用 foo 无关。怎么回事?

编辑:

让我感到困惑的不仅是为什么未注释的版本不能编译,还有为什么 可以编译 ,而 foo(a: String) 被注释掉了。我通过添加方法 foo(a: String)?

改变了什么

Set 不变并不能解释为什么它在 foo(a: String) 被注释掉时编译成功。

其实...这个问题的真正答案隐藏在@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 编译器的大师 "the programmer" 怎么会出错。所以它检查它是否真的可以将其推断为 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编译器的"intelligence"中。 Scala 编译器现在可以看到两个 Sample.foo。第一个想要一个 Set[A],另一个想要一个 String。 Scala 应该如何决定程序员想要哪一个。查看已知信息,Scala 发现一些看起来更像 Set[B].

的东西

现在我们讨论了类型实例化和推断,一旦 scala 知道期望的类型,它就可以尝试推断该类型。但是这里 Scala 无法决定期望的类型,因为它会看到多种选择。所以在转向类型推断之前,它应该处理重载选择的问题,然后才能设置它对推断的期望。

这在 Scala 规范的 Overload Resolution ( Section 6.26.3) 中进行了讨论。该规范可能看起来有点不透明,所以让我们讨论一下它是如何尝试区分的。

overload resolution实际上是由两个问题组成的,

  • 问题 1 :: 仅考虑提供的论点,在所有备选方案中,哪个更具体 applicable。换句话说,我们会查看 Applicability 可用替代方案的参数。 ApplicabilitySection 6.6 中讨论。 Applicability 首先考虑提供的参数的形状,并严重依赖于每个类型参数的 CompatibilityConformance 以供进一步分析。

  • 问题 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中(只是为了更容易理解上面解释的实际问题,不应该以任何方式被视为准确)但简单的解释 - >你们都应该知道除了 type-inference Scala 在那些神奇的隐式 type-class 的帮助下还有另一个神奇的东西叫 implicit conversions。 Scala 编译器现在可以看到两个 Sample.foo。第一个想要一个 Set[A],另一个想要一个 String。但是 Scala 看起来更像是 Set[B]。现在它可以尝试 infer 作为 Set[A] 或尝试 implicitly convertString.

这两个选择在 Scala 看来都相当合理,现在这个 "intelligent" 对它高贵的主人 "the programmer" 想要什么感到困惑。它不敢在主人的事情上出什么差错,决定把自己的困惑告诉主人,征求主人的意见。

现在...我们程序员如何帮助解决它的困惑...好吧,我们只是提供更多信息。

例如,

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

在工作情况下,Set.apply[T] 的类型参数被推断为 A 因为 Set[A] 是函数参数的预期类型。

重载解析会在没有预期类型的​​情况下对参数进行类型检查,因此编译器无法再使用 Set[A] 来指导推断您想要的集合。

这是 the spec 的一个重要收获,尽管现在它有点被关于 SAM 的更多词汇所掩盖。

Otherwise, let Si... be the list of types obtained by typing each argument as follows. [Something about functions.] All other arguments are typed with an undefined expected type.

如果它知道 Set[A] 是预期的,那么你的集合就是这样输入的,而不是 Set[B]

您可以使用 -Ytyper-debug 来观察输入决策,它发出的输出有时并非难以理解。

给出

class A ; class B extends A

object X { def f(as: Set[A]) = ??? ; def f(s: String) = ??? }

object Main extends App {
  X.f(Set(new B))
}

在这里,值参数的类型为 Set[B],然后它尝试并未能找到到重载参数类型的隐式转换。

它还会查找 X 对象到具有采用 Set[B].

f 方法的类型的转换
|    |-- X.f(Set(new B())) BYVALmode-EXPRmode (site: value <local Main> in Main)
|    |    |-- X.f BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |-- X EXPRmode-POLYmode-QUALmode (silent: value <local Main> in Main)
|    |    |    |    \-> X.type
|    |    |    \-> (s: String)Nothing <and> (as: Set[A])Nothing
|    |    |-- Set(new B()) BYVALmode-EXPRmode (silent: value <local Main> in Main)
|    |    |    |-- Set BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |-- scala.Predef.Set.apply BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |    [adapt] [A](elems: A*)CC[A] adapted to [A](elems: A*)CC[A]
|    |    |    |    |    \-> (elems: A*)scala.collection.immutable.Set[A]
|    |    |    |    [adapt] => scala.collection.immutable.Set.type adapted to [A](elems: A*)CC[A]
|    |    |    |    \-> (elems: A*)scala.collection.immutable.Set[A]
|    |    |    |-- new B() BYVALmode-EXPRmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |-- new B BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |    |-- new B EXPRmode-POLYmode-QUALmode (silent: value <local Main> in Main)
|    |    |    |    |    |    |-- B FUNmode-TYPEmode (silent: value <local Main> in Main)
|    |    |    |    |    |    |    \-> B
|    |    |    |    |    |    \-> B
|    |    |    |    |    \-> ()B
|    |    |    |    \-> B
|    |    |    solving for (A: ?A)
|    |    |    \-> scala.collection.immutable.Set[B]
|    |    [search #1] start `(s: String)Nothing <and> (as: Set[A])Nothing`, searching for adaptation to pt=scala.collection.immutable.Set[B] => String (silent: value <local Main> in Main) implicits disabled
|    |    15 implicits in companion scope
|    |    [search #2] start `(s: String)Nothing <and> (as: Set[A])Nothing`, searching for adaptation to pt=(=> scala.collection.immutable.Set[B]) => String (silent: value <local Main> in Main) implicits disabled
|    |    15 implicits in companion scope
|    |    [search #3] start `(s: String)Nothing <and> (as: Set[A])Nothing`, searching for adaptation to pt=scala.collection.immutable.Set[B] => Set[A] (silent: value <local Main> in Main) implicits disabled
|    |    15 implicits in companion scope
|    |    [search #4] start `(s: String)Nothing <and> (as: Set[A])Nothing`, searching for adaptation to pt=(=> scala.collection.immutable.Set[B]) => Set[A] (silent: value <local Main> in Main) implicits disabled
|    |    15 implicits in companion scope
|    |    second try: <error> and Set(new B())
|    |    |-- Set(new B()) EXPRmode (silent: value <local Main> in Main)
|    |    |    |-- Set BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |-- scala.Predef.Set.apply BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Main> in Main)
|    |    |    |    |    [adapt] [A](elems: A*)CC[A] adapted to [A](elems: A*)CC[A]
|    |    |    |    |    \-> (elems: A*)scala.collection.immutable.Set[A]
|    |    |    |    [adapt] => scala.collection.immutable.Set.type adapted to [A](elems: A*)CC[A]
|    |    |    |    \-> (elems: A*)scala.collection.immutable.Set[A]
|    |    |    solving for (A: ?A)
|    |    |    \-> scala.collection.immutable.Set[B]
|    |    [search #5] start `X.type`, searching for adaptation to pt=X.type => ?{def f(x: ? >: scala.collection.immutable.Set[B]): ?} (silent: value <local Main> in Main) implicits disabled
|    |    [search #6] start `X.type`, searching for adaptation to pt=(=> X.type) => ?{def f(x: ? >: scala.collection.immutable.Set[B]): ?} (silent: value <local Main> in Main) implicits disabled
badset.scala:7: error: overloaded method value f with alternatives:
  (s: String)Nothing <and>
  (as: Set[A])Nothing
 cannot be applied to (scala.collection.immutable.Set[B])