【问题标题】:Scala generic type inference inconsistent with ClassTagScala 泛型类型推断与 ClassTag 不一致
【发布时间】:2016-07-27 03:43:38
【问题描述】:

我正在使用 Scala 2.10,并且发现在对泛型参数使用类型推断时我认为奇怪的行为。考虑下面的例子(注意i 只是一个虚拟变量):

scala> import scala.reflect.{ClassTag,classTag}
     |
     | def broken[T : ClassTag](i : Int) : List[T] = {
     |   println("ClassTag is : " + classTag[T])
     |   List.empty[T]
     | }

import scala.reflect.{ClassTag, classTag}

broken: [T](i: Int)(implicit evidence$1: scala.reflect.ClassTag[T])List[T]

scala> broken(3)
ClassTag is : Nothing
res0: List[Nothing] = List()

scala> val brokenVal : List[String] = broken(3)
ClassTag is : Nothing
brokenVal: List[String] = List()

调用broken(3) 似乎是一致的,因为函数内的打印语句与推断的ClassTag 一致。

但是,在第二个语句中,编译器为 ClassTag 推断出的并在函数内部打印的内容与实际返回类型之间存在不一致。

我本来希望编译器从类型声明中推断出ClassTag,即List[String]。这不正确吗?有人可以启发我吗?

这里有一些进一步的上下文:我实际上是在使用 classtag 来过滤某些代码中的集合(此处未显示),当我没有明确指定 T 时,它显然会失败,因为它与类型 @987654329 进行比较@。

【问题讨论】:

    标签: scala generics reflection


    【解决方案1】:

    第二个示例的实际 return 类型是 List[Nothing],因此 ClassTag 与它完全一致。这是因为List 是协变的(定义为List[+A] 而不仅仅是List[A])。这种协方差允许您编写类似list = Nil 的语句(因为Nil.type = List[Nothing]),但它也允许编译器将底部类型推断为列表类型参数,因为List[Nothing]List[String] 的子类型,同样,由于它的协方差。所以实际的类型不匹配是在返回类型和brokenVal的类型之间。

    要克服这个限制,您可以使用不变类型作为返回类型,即:

    import scala.reflect.ClassTag
    
    class Invariant[T]
    
    def f[T: ClassTag](i: Int): Invariant[T] = {
      println("ClassTag: " + implicitly[ClassTag[T]])
      new Invariant[T]
    }
    
    val ret: Invariant[String] = f(3)
    // ClassTag: java.lang.String
    // ret: Invariant[String] = Invariant@30af5b6b
    

    另一方面,您可以让编译器从其他东西推断T,例如,通过传递T 类型的值作为参数。你不能让这个例子在 vanilla Lists 上工作,因为你不能改变它们的变化。

    【讨论】:

    • 我实际上是在使用 rx Observables,它们也是协变的,并且遇到了这个问题。我在collect 语句中使用ClassTag 来仅获取某种类型的项目。因为我使用类型本身来过滤掉东西,即observable collect { case t : T => t },所以没有值可以作为参数传入,就像你说的那样,这可以解决推理问题。感谢您的反馈。
    • 即使在这种情况下也有一种可能的宏解决方案来收集类型签名,但我认为在这里使用宏有点过头了。只需省略变量类型并明确指定[T]
    【解决方案2】:

    一种可能的解决方案是使用NotNothing 类型类来确保编译时类型安全。

    这里解释Possible to make scala require a non-Nothing generic method parameter and return to type-safety

    scalaZ 有一个您可以使用的NotNothing 类。

    def broken[T : ClassTag : NotNothing](i : Int) : List[T] = {
      println("ClassTag is : " + classTag[T])
      List.empty[T]
    }
    

    【讨论】:

      猜你喜欢
      • 2012-11-29
      • 2023-03-08
      • 2017-09-08
      • 2013-06-04
      • 1970-01-01
      • 1970-01-01
      • 2020-10-12
      • 1970-01-01
      • 2014-02-04
      相关资源
      最近更新 更多