当方法以看似不相关的方式重载时,为啥 scala 无法编译?
Posted
技术标签:
【中文标题】当方法以看似不相关的方式重载时,为啥 scala 无法编译?【英文标题】:Why does scala fail to compile when method is overloaded in a seemingly unrelated way?当方法以看似不相关的方式重载时,为什么 scala 无法编译? 【发布时间】:2017-02-15 13:51:34 【问题描述】: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)
被注释掉时它编译成功。
【问题讨论】:
重载和类型推断以有时令人惊讶的方式相互作用。没有重载,参数Set(new B)
的类型被推断为Set[A]
。使用重载,参数的类型是独立推断的(不参考可能预期的类型),这会导致 Set[B]
,它不会编译。
@TravisBrown 您应该将其添加为答案,因为它似乎是正确的。
@DenisRosca 是的,我真的很喜欢 TravisBrown 简洁明了的回答。我不能接受评论作为答案:p
【参考方案1】:
在工作情况下,Set.apply[T]
的类型参数被推断为 A
,因为 Set[A]
是函数参数的预期类型。
重载解析类型检查没有预期类型的参数,因此编译器不能再使用Set[A]
来指导推断您想要的集合。
这是 the spec 的一个重要收获,尽管现在它被更多关于 SAM 的词所掩盖。
否则,设 Si... 是通过键入每个类型获得的类型列表 论据如下。 [关于函数的一些事情。]所有其他参数 使用未定义的预期类型键入。
如果它知道 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))
这里,value 参数的类型为Set[B]
,然后它尝试并未能找到重载参数类型的隐式转换。
它还寻找将X
对象转换为具有f
方法的类型,该方法采用Set[B]
。
| |-- 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$1: ? >: 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$1: ? >: 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])
【讨论】:
您似乎遗漏了一些东西,请阅读规范的这一部分 - ` [Spec 6.26.1 ](scala-lang.org/files/archive/spec/2.12/…). Overloading resolution is just one of the seven approaches used by Scala compiler when it encounters an expression
e` 有一些值类型 T
但类型-检查了一些预期的类型pt
。
@SarveshKumarSingh 我添加了调试输出。我希望这对你来说很清楚。
是的......你看......隐式用于确定Compatibility
。在各种此类场合查看调试输出后,我推断出我的隐含理论。在我的答案中添加了一些规范细节。
无论如何,这个练习对于通过调试找到一个小错误很有用:github.com/scala/scala/pull/5444【参考方案2】:
实际上...这个问题的真正答案隐藏在@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.foo
s。首先想要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.foo
s。首先想要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
【讨论】:
类型推断用于解析多态函数。他的foo
不是多态的。
类型推断用于在未提供显式类型时确定最适合引用的类型。它实际上与多态函数无关或不限于多态函数。只是多态函数依赖于这种能力,反之亦然。例如在val i = 5
的情况下,i
被推断为Int
,如果我们写val i = 5d
,那么i
被推断为Double
并且val i = 5
或@中不涉及多态函数987654381@.
Not according to the specification: “本地类型推断推断类型参数将传递给多态类型的表达式。”
也许这种情况下的类型推断是指Set[A]
类型的推断,因为没有指定类型,因为规范说“多态类型”。
正如我所说...type-inference
是 Scala 的一项基本功能,它不仅被多态函数使用,还被许多其他东西使用,如 higher-kinded-types
、dependent-types
等。这里说基本,我的意思是它是 Scala 的基础构建块之一。以上是关于当方法以看似不相关的方式重载时,为啥 scala 无法编译?的主要内容,如果未能解决你的问题,请参考以下文章