【问题标题】:Why do we have to explicitly specify the ClassTag typeclass为什么我们必须显式指定 ClassTag 类型类
【发布时间】:2015-05-17 23:04:57
【问题描述】:

既然 scala 已经迭代到使用 ClassTag 类型类修复 JVM 类型擦除,为什么它是一个选择加入,而不是让编译器总是捕获类型签名以进行运行时检查。拥有一个隐式参数化类型约束将可以调用classTag[T],而不管泛型参数声明如何。

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

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

【问题讨论】:

    标签: scala typeclass scala-reflect


    【解决方案1】:

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

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

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

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

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

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

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

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

    此外,在我们(至少是我)使用的大多数参数化方法中,类型擦除并不是那么大的问题。由于上述原因,我认为自动为每个类型参数要求 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.
    

    为每个类实例添加ClassTag 会增加开销,并且肯定还会破坏兼容性。在很多地方也是不可能的。我们不能只给java.lang.String 注入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

    【讨论】:

    • 我更多地考虑将 ClassTag 隐式存储为可用于 scala 代码的类元数据,而不是实际重写签名以使用 ClassTag。我想这只是我在 .NET 中的反射经验让我对类型擦除更加敏感
    • 如果您将ClassTag 存储在实际的类中,那么我们不会比仅使用getClassclassOf[A] 更好。方法类型参数将更难恢复,因为我们依赖于传递的实例而不是类型参数本身。如果没有隐式的ClassTag,我的find 方法将永远无法工作,因为A 已被删除。
    • 啊,对。我给ClassTag 灌输了比它更多的功能。它并没有在编译代码中检测类型,而是从调用实例传递证据,这意味着它不是神奇的 erasure 治愈方法。感谢您提供详细信息
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2018-09-01
      • 1970-01-01
      • 2019-08-27
      • 2014-08-28
      • 2012-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多