当方法以看似不相关的方式重载时,为啥 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 发现fooSet[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 首先考虑提供的参数的形状,并在很大程度上依赖于每个类型参数的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 中(只是为了更容易地可视化上面解释的实际问题,不应该以任何方式被视为准确)而是简单的解释 -> 除了@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

【讨论】:

类型推断用于解析多态函数。他的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-typesdependent-types 等。这里说基本,我的意思是它是 Scala 的基础构建块之一。

以上是关于当方法以看似不相关的方式重载时,为啥 scala 无法编译?的主要内容,如果未能解决你的问题,请参考以下文章

为啥“避免方法重载”?

为啥重载的scala函数需要返回类型?

当 Row 接受可变参数时,为啥 Scala 编译器会失败并显示“此处不允许 ': _*' 注释”?

为啥 Typescript 不以正确的方式支持函数重载?

为啥没有为不同的返回类型定义方法重载?

Scala:编译错误:方法重载