2017-04-05 37 views
6

无法编译为什么下面没有在斯卡拉编译:为什么超载具有不同的上限多态的方法斯卡拉

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会抱怨呢?

+2

我发现这个旧帖子似乎是一样的问题。 http://www.scala-lang.org/old/node/4625.html – mdm

+0

因此,在阅读完该线程后,这意味着它只是“Scala编译器中的一个bug,它可能永远不会被修复;但希望能在Dotty中工作。另外,如果您将此评论重新发布为答案,我会接受它作为正确答案。 –

回答

3

重新发布评论作为提高知名度的答案。

我发现这个旧帖子什么似乎是同样的问题:http://www.scala-lang.org/old/node/4625.html

这似乎是一个已知问题与Scala编译器,不必做更多的事实,这将是困难的在不破坏其他(仅限Scala)功能和保证编译器的情况下支持此功能。该帖子还显示了几个解决方法。

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

+3

只有当您指定超过20个字符的类型(最好是德语)时,Skala才会支持它,因此编译器可以轻松区分经典和高级的情况;) – dk14

2

这不仅斯卡拉支持这一点,的Java不支持这个太:

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

这等于的Java

public <Q extend A> Q f(q: Q) { return q;} 
public <Q extend B> Q f(q: Q) { return q;} 

正如我们所知道的,型擦除将在运行时删除类型,例如,有一个类型为CAB这两个子类型,在运行时,它会混淆哪个f应该是apply

有一个代码片段也许是有助于理解这一点:

由于斯卡拉trait

trait A 
trait B 

class C extends A with B 

所以CAB。如果将其传递给f方法,将会导致运行时中的混淆。

所以在的Java是一样的,我们可以用interface陈述。这也会造成运行时间的混乱。例如:

interface C { 
} 
interface D { 
} 
static class A implements D, C { 
} 
public <Q extends C> Q f(Q q) { 
    return q; 
} 
public <Q extends D> Q f(Q q) { 
    return q; 
} 
new TestTmp().f(new A()) // Ambiguous call in here. 
+0

但是发出的类型不是'Object'类型。它们分别是'q:A'和'q:B'类型,因为它们有一个上限。 –

+0

@YuvalItzchakov,我已经为这一点添加了一个'混合trait'(用于** Java **的接口)示例,或许它有帮助 – chengpohi

+0

这是不正确的,正如http://stackoverflow.com/a/43229647/5483583 –

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 = [email protected] 

scala> (new B{}).f 
res1: B = [email protected] 

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

之前执行方法invokation的类和方法确定由解决。有关如何解决方法的说明,请参阅第9章。

invokestatic查看给定的描述符,并且 确定该方法需要多少个参数(可能为零)。它 从操作数堆栈中弹出这些参数。然后它搜索该类定义的静态方法列表 ,使用描述符描述符定位方法方法名 。

所以,不管它知道Q <: A限制 - 它不知道在正式运行时类型的Q,所以有些情况下,像一个由@chengpohi指出似乎无法检测或解决(其实他们可以根据来自线性化的信息来做到这一点 - 唯一的缺点是运行时类型参与调度)。


哈斯克尔,例如,确定正确的方法在编译时(据我所知),这样类型的类都能够通过决定正确的方法来调用,以弥补缺乏真正的静态调度的在编译时。

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

2

我想指出的是,以上是可能的Java。它的行为如预期的ab。我可以看到Scala中的特性问题,因为有多个mixin,但是类的多重继承是不可能的。

public class A {} 
public class B {} 

public class Test { 
    public <Q extends A> Q f(Q q) { 
     System.out.println("with A"); 
     return q; 
    } 

    public <Q extends B> Q f(Q q) { 
     System.out.println("with B"); 
     return q; 
    } 

    public static void main(String[] args) { 
     final A a = new A() {}; 
     final B b = new B() {}; 

     final Test test = new Test(); 

     test.f(a); 
     test.f(b); 
    } 
}