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

Scala 为什么我们必须显式地指定ClassTag类型类,scala,typeclass,scala-reflect,Scala,Typeclass,Scala Reflect,既然scala已经使用ClassTagtypeclass迭代了JVM类型擦除修复,为什么它是一个选择性加入,而不是让编译器始终捕获类型签名以供运行时检查。使用隐式参数化类型约束可以调用classTag[T],而不考虑泛型参数声明 编辑:我应该澄清,我不是说scala应该在幕后将签名更改为始终包含类标签。相反,我的意思是,既然ClassTag表明scala可以捕获运行时类型信息,从而避免类型擦除限制,为什么不能将该捕获隐式地作为编译器的一部分,以便该信息在scala代码中始终可用 我怀疑是向后兼容

既然scala已经使用
ClassTag
typeclass迭代了JVM类型擦除修复,为什么它是一个选择性加入,而不是让编译器始终捕获类型签名以供运行时检查。使用隐式参数化类型约束可以调用
classTag[T]
,而不考虑泛型参数声明

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


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

向后兼容性将被彻底破坏,真的。如果您有一个简单的方法,如:

def foo[A](a: A)(implicit something: SomeType) = ???
然后假设在Scala的下一个版本中,编译器突然将隐式
ClassTag
s添加到所有具有类型参数的方法的签名中。这种方法将被打破。无论它在什么地方被显式调用,比如
foo(a)(someTypeValue)
都不再有效。二进制和源代码的兼容性将消失

Java互操作性将是丑陋的。假设我们的方法现在如下所示:

def foo[A : ClassTag](a: A) = ???
由于
ClassTag
s是由Scala编译器生成的,因此从Java使用此方法将更加困难。您必须自己创建
ClassTag

ClassTag<MyClass> tag = scala.reflect.ClassTag$.MODULE$.apply(MyClass.class);
foo(a, tag);
我们还应该注意到,它们也不是完美的。因此,添加它们并不是擦除问题的最终解决方案

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
中注入
ClassTag
。此外,我们也同样容易被擦除。在每个类中都有一个
ClassTag
字段实际上并不比使用
getClass
好多少。我们可以做这样的比较

case a if(a.getClass == classOf[String]) => a.asInstanceOf[String]
但这非常难看,需要演员阵容,而且不一定是
ClassTag
想要解决的问题。如果我用我的
find
方法尝试类似的方法,它根本不起作用


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

是的,对于很少使用的用例(例如,需要数组构造或异构映射值恢复),您将惩罚任何通用方法或类。有了“始终使用类标记”的思想,您还可以有效地消除从Java调用Scala代码的可能性。总之,无论从兼容性、性能还是类大小的角度来看,要求类标记始终存在都没有任何意义。

我更多地考虑将类标记隐式存储为scala代码可用的类元数据,而不是实际重新编写签名以使用类标记。我想这只是我在.NET中使用反射的经验,它使我对类型擦除更加敏感。但是,如果您将
ClassTag
存储在实际的类中,那么我们使用
getClass
classOf[A]
并没有比使用
。方法类型参数将更难恢复,因为我们将依赖于传递的实例,而不是类型参数本身。如果没有隐式的
ClassTag
,我的
find
方法将永远无法工作,因为
A
被擦除。啊,对了。我给
ClassTag
注入了比它更多的功能。它不会在编译代码中插入类型,而是从调用实例传递证据,这意味着它不是神奇的擦除疗法。谢谢你的详细信息
case a if(a.getClass == classOf[String]) => a.asInstanceOf[String]
// Can't compile
def find[A](l: List[Any]): Option[A] =
    l collectFirst { case a if(a.getClass == classOf[A]) => a.asInstanceOf[A] }