【发布时间】:2009-08-31 15:04:33
【问题描述】:
我最近开始学习 Scala,很失望(但并不惊讶)他们的泛型也是通过类型擦除实现的。
我的问题是,Scala 是否有可能具有具体化的泛型,或者 JVM 是否需要以某种方式进行更改?如果 JVM 确实需要改变,究竟需要改变什么?
【问题讨论】:
我最近开始学习 Scala,很失望(但并不惊讶)他们的泛型也是通过类型擦除实现的。
我的问题是,Scala 是否有可能具有具体化的泛型,或者 JVM 是否需要以某种方式进行更改?如果 JVM 确实需要改变,究竟需要改变什么?
【问题讨论】:
否 - 如果该字节码不支持具体化的泛型,Scala 就不可能作为与 Java 等效的字节码运行。
当你问“需要改变什么?”,答案是:字节码规范。目前,字节码不允许定义变量的参数化类型。已决定将break backwards compatibility 和generics would have to be implemented via type erasure 作为对字节码的修改以支持具体化的泛型。
为了解决这个问题,Scala 利用其implicit 机制的强大功能定义了一个Manifest,可以在任何范围内导入它以在运行时发现类型信息。清单是实验性的,基本上没有记录,但它们 are coming as part of the library in 2.8。这是Scala reified generics / Manifests上的另一个好资源
【讨论】:
为了补充 oxbow_lakes,Stack Overflow 上有一个关于 how to get around type erasure in Scala 的问题。
【讨论】:
"implicit Manifest" 是一个 Scala 编译器技巧,它不会使 Scala 中的泛型具体化。 Scala 编译器,当它看到一个带有“implicit m:Manifest[A]”参数的函数并且它知道调用站点上 A 的泛型类型时,它将包装 A 的类及其泛型在 Manifest 中输入参数并使其在函数中可用。但是,如果它无法确定 A 的真实类型,那么它就无法创建 Manifest。换句话说,如果内部函数需要,Manifest 必须沿着函数调用链传递。
scala> def typeName[A](a: A)(implicit m: reflect.Manifest[A]) = m.toString
typeName: [A](a: A)(implicit m: scala.reflect.Manifest[A])java.lang.String
scala> typeName(List(1))
res6: java.lang.String = scala.collection.immutable.List[int]
scala> def foo[A](a: A) = typeName(a)
<console>:5: error: could not find implicit value for parameter m:scala.reflect.Manifest[A].
def foo[A](a: A) = typeName(a)
^
scala> def foo[A](a: A)(implicit m: reflect.Manifest[A]) = typeName(a)
foo: [A](a: A)(implicit m: scala.reflect.Manifest[A])java.lang.String
scala> foo(Set("hello"))
res8: java.lang.String = scala.collection.immutable.Set[java.lang.String]
【讨论】:
补充 oxbow_lakes 的回答:这是不可能的,而且似乎永远不会发生(至少很快)。
JVM 不支持物化泛型的(可反驳的)原因似乎是:
参考资料:
Odersky comment in 2010:“我更喜欢带有类型擦除的更简单的 VM 架构”
在 scala-internals 列表中(2013 年 2 月)Grzegorz Kossakowski said:
您可以轻松地对其进行基准测试,并看到它对性能的影响非常 显。尤其是内存消耗增加很多。
我相信要走的路是按照我们的方式进行可选的具体化 开始在 Scala 中使用 Manifests/TypeTags。
如果可以,并将其与运行时专业化相结合,您可以瞄准 高性能和通用代码。然而,这可能是目标 Scala 2.12 或 2.13。
【讨论】:
一旦 scalac 成为编译器,它就有可能使用实现具体泛型所需的任何数据结构来修饰生成的代码。
我的意思是 scalac 将有能力看到...
// definition
class Klass[T] {
value : T
}
//calls
floats = Klass[float]
doubles = Klass[double]
...并“扩展”成这样的东西:
// definition
class Klass_float {
value : float
}
class Klass_double {
value : double
}
// calls
floats = Klass_float
doubles = Klass_double
编辑
重点是:编译器有能力创建所有必要的数据结构,这些数据结构证明在运行时提供额外的类型信息是必要的。一旦这个类型信息可用,Scala 运行时就会利用它,并可以执行我们可以想象的所有类型感知操作。 JVM 是否为具体化的泛型提供字节码并不重要。这项工作不是由 JVM 完成的,而是由 Scala 库完成的。
如果你已经写了一个符号调试器(我写了!),你知道你基本上可以将编译器在编译时拥有的所有信息“转储”到生成的二进制文件中,采用任何数据组织证明更方便进一步处理。这是完全相同的想法:“转储”Scala 编译器拥有的所有类型信息。
简而言之,我不明白为什么它不可能,这与 JVM 是否为具体泛型提供本机操作无关。 JVM 字节码与具体化的泛型无关。这种事情是语言规范、编译器特性和运行时库支持的问题。
另一个修改
IBM X10 展示了我所说的能力:它将 X10 代码编译为 Java 代码,在 Java 平台上利用具体化的泛型。正如我之前提到的:它可以做到(IBM X10 做到了!)但是这种特性涉及语言规范、编译器支持(或编译器插件)以及运行时库中的足够支持。更多信息请访问:http://x10.sourceforge.net/documentation/papers/X10Workshop2012/slides/Takeuchi.pdf
【讨论】:
RichardsFoo 一起使用,不是吗?