我正在挖掘新的scala反射api,并且无法弄清楚为什么以下代码段不能按预期工作。给定层次结构(试图尽可能地简化):
import scala.reflect.runtime.universe._
trait TF[A] {
implicit def t: TypeTag[A]
def f[T <: A: TypeTag]: PartialFunction[Any, A] = {
case msg: T if typeOf[T] =:= typeOf[A] => msg
}
}
class TFilter[T: TypeTag] extends TF[T] {
def t = typeTag[T]
}
case class Foo(x: Int)
我希望方法f
能够过滤给定类型的对象。因此,以下代码段应返回Seq[Foo]
val messages = Seq(1, "hello", Foo(1))
val tFilter = new TFilter[Foo]
messages collect tFilter.f[Foo]
它实际上会返回Seq[Foo]
,但其他消息未经过滤,这听起来像是一个错误。
res1: Seq[Foo] = List(1, hello, Foo(1))
问题即可。我使用TypeTag
错了还是新反映api的缺陷?
PS0 即可。尝试使用Scala 2.10.0-RC1
和2.10.0-RC2
PS1 即可。解决方法是将TypeTag
替换为Manifest
,因此序列中的以下代码collect
将按预期返回List(Foo(1))
。
trait MF[A] {
implicit def m: Manifest[A]
def f[T <: A: Manifest]: PartialFunction[Any, A] = {
case msg: T if typeOf[T] =:= typeOf[A] => msg
}
}
class MFilter[T: Manifest] extends MF[T] {
def m = manifest[T]
}
更新:与新Scala 2.10.0-RC2
版本相同。
答案 0 :(得分:6)
所以我认为这里的关键问题是你需要匹配msg
的类型,但它的编译时类型是Any
(来自PartialFunction
声明)。从本质上讲,您需要为TypeTag
中的每个元素添加不同的List[Any]
。但是因为它们都被编入时间类型为Any
,因为所有这些都被放入同一个列表中,所以你不会得到一个比这更具体的TypeTag
。
我认为您可能想要做的是使用ClassTag
代替TypeTag
:
trait TF[A] {
implicit def t: ClassTag[A]
def f: PartialFunction[Any, A] = {
case msg: A => msg
}
}
class TFilter[T: ClassTag] extends TF[T] {
def t = classTag[T]
}
case class Foo(x: Int)
val messages = Seq(1, "hello", Foo(1), List(1), List("a"))
messages collect new TFilter[Foo].f // produces List(Foo(1))
作为Ajran points out,就像Manifest
版本一样,您必须了解运行时类型的所有限制,包括擦除和装箱问题:
messages collect new TFilter[List[Int]].f // produces List(List(1), List("a"))
messages collect new TFilter[Int].f // produces List()
messages collect new TFilter[java.lang.Integer].f // produces List(1)
有一些关于如何使TypeTag
对模式匹配更有用的建议(例如SI-6517),但我认为这些只会在你使用有用的{{ 1}},而不是编译时类型为TypeTag
的对象。
答案 1 :(得分:2)
实际上你没有在这里检查msg的类型,编译器会警告你msg:T被删除了,所以你要检查的是TFilter上定义的类型与函数f上定义的类型相同。 / p>
我看起来模式匹配是由Manifest和ClassTag“辅助”的,所以msg:T确实是一个正确的类型。如果您使用基元或List [T]进行尝试,它将无法正常工作。
val mFilter = new MFilter[Int]
messages collect mFilter.f[Int] // res31: Seq[Int] = List()
val messages = List(List(1), List("a"))
val mFilter = new MFilter[List[Int]]
messages collect mFilter.f[List[Int]] // res32: List[List[Int]] = List(List(1), List(a))
看看这个讨论: http://grokbase.com/t/gg/scala-user/126p8eh1w0/how-to-make-typetag-work-in-a-pattern
bug“在模式匹配时使用TypeTags,否则将删除类型”:here
答案 2 :(得分:2)
只是为了好玩:
import scala.reflect._
import scala.reflect.runtime.{currentMirror=>cm,universe=>ru}
import ru._
object Test extends App {
type MyTag[A] = TypeTag[A]
//type MyTag[A] = ClassTag[A]
trait TF[A] {
implicit def t: MyTag[A]
def f[T <: A: MyTag]: PartialFunction[Any, A] = {
//case msg: T => msg // ok for ClassTag
case msg: T @unchecked if matching[T](msg) => msg
//case msg: T if typeOf[T] =:= typeOf[A] => msg
}
def matching[T](a: Any)(implicit tt: TypeTag[T]) =
(cm reflect a).symbol.toType weak_<:< tt.tpe
}
case class TFilter[A: MyTag]() extends TF[A] {
def t = implicitly[MyTag[A]]
}
trait Foo { def x: Int }
case class Bar(x: Int) extends Foo
case class Baz(x: Int) extends Foo
val messages = Seq(1, Bar(0), "hello", Baz(1))
println(messages collect TFilter[Foo].f[Foo])
println(messages collect TFilter[Foo].f[Bar])
}
答案 3 :(得分:0)
感谢大家的反馈。想想,我已经找到了ClassTag
应该用于模式匹配的原因。
我设法找到了[SI-5143] Pattern matching on abstract types doesn't work,并且associated commit解释说应该有一个ClassTag
的实例来使模式可以检查。
所以,是的,我使用TypeTag
错了;在模式匹配的情况下,我应该使用ClassTag
。