I'm digging new scala reflection api and can't figure out why the following snippet doesn't work as expected. Given hierarchy (tried to simplify as much as I can):
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)
I expect method f
to filter objects of given type. So the following snippet should return Seq[Foo]
val messages = Seq(1, "hello", Foo(1))
val tFilter = new TFilter[Foo]
messages collect tFilter.f[Foo]
And it actually returns Seq[Foo]
but with other messages unfiltered, which sounds like a bug.
res1: Seq[Foo] = List(1, hello, Foo(1))
Question. Am I using TypeTag
wrong or it is defect of new reflection api?
PS0. Tried with Scala 2.10.0-RC1
and 2.10.0-RC2
PS1. The workaround is to replace TypeTag
with Manifest
, so with the following code collect
on sequence will return List(Foo(1))
as expected.
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]
}
Update: Same with new Scala 2.10.0-RC2
release.
TypeTags#TypeTag . A full type descriptor of a Scala type. For example, a TypeTag[List[String]] contains all type information, in this case, of type scala. List[String] .
Scala reflection enables a form of metaprogramming which makes it possible for programs to modify themselves at compile time. This compile-time reflection is realized in the form of macros, which provide the ability to execute methods that manipulate abstract syntax trees at compile-time.
A Manifest[T] is an opaque descriptor for type T. Its supported use is to give access to the erasure of the type as a Class instance, as is necessary for the creation of native Arrays if the class is not known at compile time.
So I think the key problem here is that you need to match against the type of msg
, but its compile-time type is Any
(from the PartialFunction
declaration). Essentially, you want a different TypeTag
for each element in your List[Any]
. But since they all have compile-time type of Any
by virtue of all being put into the same list, you're not going to get a TypeTag
that's any more specific than that.
I think what you probably want to do is use ClassTag
instead of 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))
As Ajran points out, just like the Manifest
version, you'll have to be aware of all the limitations of runtime types including erasure and boxing issues:
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)
There are some suggestions about how to make TypeTag
more useful for pattern matching (e.g. SI-6517), but I think those will only help when you're matching against an object with an useful TypeTag
, not an object with compile-time type of Any
.
actually you don't check the type of msg here, compiler will warn you that msg: T is erased, so all you are left checking is that type defined on TFilter is the same as a type defined on function f.
I looks like pattern matching is "assisted" by Manifest and ClassTag, so msg: T is indeed a correct type. If you try it with primitives or List[T] it will not work correctly.
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))
Look at this discussion: http://grokbase.com/t/gg/scala-user/126p8eh1w0/how-to-make-typetag-work-in-a-pattern
bug "Use TypeTags when pattern-matching otherwise erased types": here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With