Scala 2.10 TypeTag usage

醉酒当歌 提交于 2019-12-03 10:29:56

问题


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.


回答1:


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.




回答2:


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




回答3:


Just for fun:

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])
}



回答4:


Thanks everyone to feedback. Think, I've found the reason why ClassTag should be used in pattern matching.

I've managed to find [SI-5143] Pattern matching on abstract types doesn't work, and it's associated commit explains that there should be an instance of ClassTag to make pattern checkable.

So, yes, I used TypeTag wrong; in case of pattern matching I should use ClassTag.



来源:https://stackoverflow.com/questions/13233728/scala-2-10-typetag-usage

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!