2016-04-06 34 views
3

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

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明确,因为它是比较反对Nothing类型。

回答

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] = [email protected] 

在另一边,你可能让编译器从别的推断T,例如,通过传递作为参数的类型为T的值。你不能让这个例子在香草List上工作,因为你不能改变它们的方差。

+0

我实际上使用rx Observables,它也是协变的,并且遇到了这个问题。我在'collect'语句中使用ClassTag只拾取某种类型的项目。因为我使用类型本身来过滤东西,即'observable collect {case t:T => t}',所以没有任何价值可以作为参数传入,像你说的那样可以解决推理问题。感谢您的反馈意见。 – Luciano

+0

即使在这种情况下,仍然有可能收集类型签名的宏解决方案,但我认为在这里使用宏是矫枉过正的。只需省略变量类型并明确指定'[T]'。 – Maxim