现在,scala已使用ClassTag类型类迭代到JVM类型擦除修复程序,为什么要选择启用它,而不是让编译器始终捕获类型签名以进行运行时检查。有了它隐式的参数化类型约束,将可以调用classTag[T],而不管泛型参数声明如何。

编辑:我应该澄清一下,我并不是说scala应该将幕后的签名更改为总是包括ClassTag。相反,我的意思是,由于ClassTag显示scala可以捕获运行时Type信息并因此避免了类型擦除的限制,为什么不能在编译器中隐式捕获捕获的内容,以使该信息始终可在scala代码中使用?

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

最佳答案

确实,向后兼容性将被完全破坏。如果您有一个简单的方法,例如:

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

然后假设在下一版本的Scala中,编译器突然向所有带有类型参数的方法的签名中添加了隐式ClassTag。该方法将被破坏。像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了,那么必须这样做的方法的类将大大增加。

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

当然,这将增加更多的编译器开销,因为它将比通常需要生成更多的ClassTag。我认为除非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.

向每个单个类实例添加ClassTag会增加开销,并且肯定还会破坏兼容性。这在很多地方也是不可能的。我们不能只用java.lang.String注入(inject)ClassTag。此外,我们仍然容易受到擦除的影响。每个类中都有一个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

关于scala - 为什么我们必须显式指定ClassTag类型类,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30293025/

10-11 09:05