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