miah
miah

Reputation: 8609

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

Upvotes: 1

Views: 603

Answers (2)

Landei
Landei

Reputation: 54584

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 Manifestcan do that, but then the investigation is more complicated than calling isInstance on the underlying class.

Upvotes: 3

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297195

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.

Upvotes: 1

Related Questions