为什么重载具有不同上限的多态方法无法在Scala中编译

时间:2017-04-05 09:39:12

标签: scala overloading type-erasure

为什么以下内容无法在Scala中编译:

class A
class B

object X {
  def f[Q <: A](q: Q): Q = q
  def f[Q <: B](q: Q): Q = q
}

带有错误消息

<console>:16: error: method f is defined twice
  conflicting symbols both originated in file '<console>'
         def f[Q <: B](q: Q): Q = q

根据我的理解,在类型擦除之后,def f[Q <: A](q: Q): Q应该替换为其上限:def f(q: A): Any和第二个重载f。因此,在类型擦除后它们应该是可区分的。

那么为什么Scala还在抱怨?

4 个答案:

答案 0 :(得分:5)

为了补充@chengpohi的答案,您实际上可以使用类型类实现静态调度(重载是一种特殊情况):

trait A
trait B

implicit class RichA[Q <: A](q: Q){ def f = q }

implicit class RichB[Q <: B](q: Q){ def f = q }

scala> (new A{}).f
res0: A = $anon$1@39c1fe0b

scala> (new B{}).f
res1: B = $anon$1@20011bf

它不能自然工作的原因只是Scala必须模仿Java的重载(擦除)以保持代码与外部Java代码和Scala的内部功能和保证兼容。在你的情况下重载(但不总是)基本上是一个静态调用,所以它可以在编译时处理,但JVM的invokestatic不幸地调度in runtime

  

在执行方法调用之前,将解析标识的类和方法。有关如何解决方法的说明,请参阅第9章。

     

invokestatic查看和中给出的描述符   确定该方法采用的参数数量(这可能为零)。它   从操作数堆栈中弹出这些参数。然后它搜索列表   由类定义的静态方法,定位方法methodname   带有描述符描述符。

所以,无论它知道Q <: A限制 - 它都不知道运行时中Q的正式类型,所以像@chengpohi指向的那些情况似乎不可能检测或解决(实际上他们可以根据线性化的信息来做 - 唯一的缺点是运行时类型涉及调度)。

例如,Haskell在编译时确定了正确的方法(据我所知),因此类型类能够通过在编译时决定正确的方法来补偿缺乏真正的静态调度

P.S。请注意,在Haskell中,重载用于动态调度(模式匹配)和静态类的类,因此与Java相比,它基本上是相反的。

答案 1 :(得分:3)

重新发布评论作为提高可见度的答案。

我发现这篇旧文章似乎是同一个问题:http://www.scala-lang.org/old/node/4625.html

对于Scala编译器来说,它似乎是一个已知问题,必须做更多事情,因为在不破坏其他(仅限Scala)功能和保证的情况下很难支持此功能。编译器。该帖子也显示了一些解决方法。

如果SO上的任何编译器专家能够阐明Dotty是否 - 或者我应该说Skala,那将是非常有趣的? ;) - 计划修复它。

答案 2 :(得分:2)

这不仅 Scala 支持此功能, Java 也不支持此功能:

consumer.poll

它等于 Java

def f[Q <: A](q: Q): Q = q
def f[Q <: B](q: Q): Q = q

众所周知,类型擦除将删除运行时中的类型,例如,类型public <Q extend A> Q f(q: Q) { return q;} public <Q extend B> Q f(q: Q) { return q;} C和{{1}的子类型在运行时,它会混淆哪个A B

有一段代码片段可能对理解这一点很有帮助:

正如 Scala f

apply

所以traittrait A trait B class C extends A with B C子类。如果将其传递给A方法,则会导致运行时中的混淆。

所以在 Java 中,我们可以使用B来声明子类。这也会导致运行时中的混淆。例如:

f

答案 3 :(得分:2)

我想指出上述内容在Java中是可行的。它的行为符合white-space: nowrap;a的预期。由于多个mixin,我可以在Scala中看到traits的问题,但是不可能有多个类的继承。

b