我使用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
进行比较。
答案 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]
}