为什么重载具有不同上限的多态方法不能在 Scala 中编译
Why does overloading polymorphic methods with different upper bounds not compile in Scala
为什么以下代码不能在 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 还是会抱怨呢?
不仅Scala支持这个,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;}
众所周知,type erasure会在运行时删除类型,例如有一个类型C
是[=15=的both子类型]和B
,在运行时,会混淆哪个f
应该apply
。
有一段代码可能有助于理解:
作为 Scala trait
:
trait A
trait B
class C extends A with B
所以 C
是 A
和 B
的 子类 。如果将它传递给 f
方法,它会在 runtime.
中引起混淆
so 在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.
只是为了补充@chengpohi 的回答,您实际上可以使用 type-类:
实现静态调度(重载是一种特殊情况)
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@39c1fe0b
scala> (new B{}).f
res1: B = $anon@20011bf
它不能自然工作的原因只是 Scala 必须模仿 Java 的重载(带有擦除)以保持代码与外部 Java 代码和 Scala 的内部代码兼容特点和保证。在你的情况下重载(但不总是)基本上是一个静态调用,所以它可以在编译时处理,但是 JVM 的 invokestatic
确实调度 in runtime 不幸的是:
Before performing the method invokation, the class and the method identified by are resolved. See Chapter 9 for a description of how methods are resolved.
invokestatic looks at the descriptor given in , and
determines how many arguments the method takes (this may be zero). It
pops these arguments off the operand stack. Then it searches the list
of static methods defined by the class, locating the method methodname
with a descriptor descriptor.
所以,不管它知道 Q <: A
限制 - 它不知道运行时 Q
的正式类型,所以像@chengpohi 指出的那样的一些情况似乎不知道可能检测或解决(实际上他们可以根据线性化的信息来做到这一点 - 唯一的缺点是运行时类型参与调度)。
例如,Haskell 在编译时确定正确的方法(据我所知),因此 type-类 能够弥补真正静态调度的不足决定在编译时调用的正确方法。
P.S。请注意,在 Haskell 中,重载用于动态调度(模式匹配),而 类 用于静态调度,因此与 Java.
相比,它基本上是相反的
我想指出以上内容在Java中是可行的。对于 a
和 b
,它的行为符合预期。由于多个混合,我可以看到 Scala 中的特征问题,但是 类 的多重继承是不可能的。
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);
}
}
重新post评论作为提高可见度的答案。
我发现这个旧 post 似乎是同一个问题:http://www.scala-lang.org/old/node/4625.html
这似乎是 Scala 编译器的一个已知问题,必须做更多的工作,因为在不破坏其他功能的情况下很难支持此功能(仅限 Scala ) 编译器的特性和保证。 post 也显示了一些解决方法。
如果 SO 上的任何编译器大师能够阐明 Dotty - 或者我应该说 Skala,那将是非常有趣的。 ;) - 将计划修复它。
为什么以下代码不能在 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 还是会抱怨呢?
不仅Scala支持这个,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;}
众所周知,type erasure会在运行时删除类型,例如有一个类型C
是[=15=的both子类型]和B
,在运行时,会混淆哪个f
应该apply
。
有一段代码可能有助于理解:
作为 Scala trait
:
trait A
trait B
class C extends A with B
所以 C
是 A
和 B
的 子类 。如果将它传递给 f
方法,它会在 runtime.
so 在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.
只是为了补充@chengpohi 的回答,您实际上可以使用 type-类:
实现静态调度(重载是一种特殊情况)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@39c1fe0b
scala> (new B{}).f
res1: B = $anon@20011bf
它不能自然工作的原因只是 Scala 必须模仿 Java 的重载(带有擦除)以保持代码与外部 Java 代码和 Scala 的内部代码兼容特点和保证。在你的情况下重载(但不总是)基本上是一个静态调用,所以它可以在编译时处理,但是 JVM 的 invokestatic
确实调度 in runtime 不幸的是:
Before performing the method invokation, the class and the method identified by are resolved. See Chapter 9 for a description of how methods are resolved.
invokestatic looks at the descriptor given in , and determines how many arguments the method takes (this may be zero). It pops these arguments off the operand stack. Then it searches the list of static methods defined by the class, locating the method methodname with a descriptor descriptor.
所以,不管它知道 Q <: A
限制 - 它不知道运行时 Q
的正式类型,所以像@chengpohi 指出的那样的一些情况似乎不知道可能检测或解决(实际上他们可以根据线性化的信息来做到这一点 - 唯一的缺点是运行时类型参与调度)。
例如,
Haskell 在编译时确定正确的方法(据我所知),因此 type-类 能够弥补真正静态调度的不足决定在编译时调用的正确方法。
P.S。请注意,在 Haskell 中,重载用于动态调度(模式匹配),而 类 用于静态调度,因此与 Java.
相比,它基本上是相反的我想指出以上内容在Java中是可行的。对于 a
和 b
,它的行为符合预期。由于多个混合,我可以看到 Scala 中的特征问题,但是 类 的多重继承是不可能的。
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);
}
}
重新post评论作为提高可见度的答案。
我发现这个旧 post 似乎是同一个问题:http://www.scala-lang.org/old/node/4625.html
这似乎是 Scala 编译器的一个已知问题,必须做更多的工作,因为在不破坏其他功能的情况下很难支持此功能(仅限 Scala ) 编译器的特性和保证。 post 也显示了一些解决方法。
如果 SO 上的任何编译器大师能够阐明 Dotty - 或者我应该说 Skala,那将是非常有趣的。 ;) - 将计划修复它。