Scala泛型类型推断与ClassTag不一致

时间:2016-04-06 15:53:13

标签: scala generics reflection

我使用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)似乎是一致的,因为函数内的print语句与推断的ClassTag一致。

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

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

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

2 个答案:

答案 0 :(得分:1)

第二个示例的实际返回类型为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 List上运行,因为你不能改变它们的方差。

答案 1 :(得分:0)

可能的解决方案是使用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]
}