为什么我们必须显式指定 ClassTag 类型类

Why do we have to explicitly specify the ClassTag typeclass

现在 Scala 已经迭代到 JVM type erasure fix with the ClassTag typeclass,为什么它是一个选择加入,而不是让编译器总是捕获运行时检查的类型签名。拥有隐式参数化类型约束将使调用 classTag[T] 成为可能,而不管泛型参数声明如何。

编辑:我应该澄清一下,我并不是说 Scala 应该在幕后更改签名以始终包含 ClassTag。相反,我的意思是,既然 ClassTag 表明 scala 可以捕获运行时类型信息并因此避免类型擦除限制,为什么不能将捕获隐式地作为编译器的一部分,以便该信息始终在 scala 代码中可用?

我怀疑它与向后兼容性、java 生态系统兼容性、二进制大小或运行时开销有关,但这些都只是推测。

向后兼容性将完全被破坏,真的。如果你有一个简单的方法,比如:

def foo[A](a: A)(implicit something: SomeType) = ???

那么假设在下一版本的Scala中,编译器突然在所有带有类型参数的方法的签名中添加了隐式的ClassTags。这种方法会被打破。在任何被显式调用的地方,如 foo(a)(someTypeValue) 将不再起作用。二进制和源兼容性将消失。

Java 互操作性会很难看。假设我们的方法现在看起来像这样:

def foo[A : ClassTag](a: A) = ???

因为 ClassTag 是由 Scala 编译器生成的,所以使用 Java 中的这种方法会更加困难。您必须自己创建 ClassTag

ClassTag<MyClass> tag = scala.reflect.ClassTag$.MODULE$.apply(MyClass.class);
foo(a, tag);

我的 Java 可能不是 100% 正确,但你明白了。任何参数化的东西都会变得非常丑陋。好吧,如果它需要一个隐式 ClassTag,它已经是这样了,但是 class 需要这种方法的方法会急剧增加。

此外,在我们(至少我)使用的大多数参数化方法中,类型擦除并不是那么大的问题。由于上述原因,我认为为每个类型参数自动要求一个 ClassTag 会麻烦多于帮助。

当然这会增加更多的编译器开销,因为它需要生成比通常更多的 ClassTags。我不 认为 它会增加更多的运行时开销,除非 ClassTag 有所作为。例如,在如下所示的简单方法中,ClassTag 并没有真正执行任何操作:

def foo[A : ClassTag](a: A): A = a

我们还应该注意到它们也不完美。所以添加它们并不是解决擦除问题的最终解决方案。

val list = List(1, "abc", List(1, 2, 3), List("a", "b"))
def find[A: ClassTag](l: List[Any]): Option[A] =
    l collectFirst { case a: A => a }

scala> find[List[String]]
res2: Option[List[String]] = Some(List(1, 2, 3)) // Not quite! And no warnings, either.

向每个 class 实例添加 ClassTag 会增加开销,并且肯定还会破坏兼容性。这在很多地方也是不可能的。我们不能只用 ClassTag 注入 java.lang.String。此外,我们仍然很容易被擦除。在每个 class 中有一个 ClassTag 字段并不比使用 getClass 好多少。我们可以进行比较

case a if(a.getClass == classOf[String]) => a.asInstanceOf[String]

但这非常丑陋,需要转换,而且不一定是 ClassTag 的目的。如果我用我的 find 方法尝试这样的事情,它根本行不通。

// Can't compile
def find[A](l: List[Any]): Option[A] =
    l collectFirst { case a if(a.getClass == classOf[A]) => a.asInstanceOf[A] }

即使我将其设计为以某种方式与 ClassTag 一起使用,它又会从何而来?我不能a.classTag == classTag[A],因为A已经被删除了。我需要方法调用站点的 ClassTag

是的,对于很少见的用例(例如,需要数组构造或异构映射值恢复),您会惩罚任何泛型方法或 class。有了 "always class tags" 这个想法,你也会有效地破坏从 Java 调用 Scala 代码的可能性。总之,无论是从兼容性、性能还是 class 大小的角度来看,要求始终存在 class 标签根本没有任何意义。