Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala 2.10 TypeTag usage

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.

like image 380
4e6 Avatar asked Nov 05 '12 14:11

4e6


People also ask

What is TypeTag in Scala?

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] .

What is reflection Scala?

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.

What is manifest in Scala?

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.


2 Answers

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.

like image 115
Steve Avatar answered Oct 04 '22 07:10

Steve


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

like image 25
Arjan Avatar answered Oct 04 '22 06:10

Arjan