Pattern matching against value with generic type
I'm writing tree-based expression evaluator, ans I've run into some troubles with type-erasure.
Tree looks like
sealed abstract class Node[+T]
case class Var[+T](name:String) extends Node[T]
/* SNIP */
The evaluator is
def eval[T](node:Node[T], context:Map[String, Any]):Option[T] = node match {
开发者_如何学JAVA case Var(name) => context.get(name) match {
case Some(value:T) => Some(value)
case _ => None
}
/* SNIP */
}
The code compiles, but type checks on Var
nodes don't work. So this test fails:
class ContextEvaluatorTest extends FunSuite with ShouldMatchers {
test("evaluation with type mismatch") {
ContextEvaluator.eval(Var[Int]("a"), Map("a" -> "not int")) should equal (None)
}
}
Error message is
org.scalatest.TestFailedException: Some(not int) did not equal None
Situation looks like a use-case for manifests, but I couldn't add them properly.
This seems to work:
def eval[T:ClassManifest](node:Node[T], context:Map[String, Any]):Option[T] = node match {
case Var(name) => context.get(name) match {
case Some(value:T) if classManifest[T].erasure.isInstance(value) => Some(value)
case _ => None
}
case _ => None
}
Note however that T
must be a simple type, AFAIK ClassManifest
can't distinguish between things like List[Int]
and List[String]
. Probably Manifest
can do that, but then the investigation is more complicated than calling isInstance
on the underlying class.
It doesn't work because T
is erased. That means value: T
in the pattern match is meaningless. In fact, the compiler should have warned you about that.
You'll have to resort to using manifests to make that test. See Landei's answer for an example.
精彩评论