Tuesday, July 7, 2009

Getting Around Type Erasure with Manifests

A friend posed an interesting problem to me yesterday. He wanted a registry-like structure in which he would put pairs of key/value, and then get them back. However, he wished for it to be type-safe. Specifically, if he put a List[Int] inside it, and tried to get a List[String] out, he shouldn’t get anything.

This is generally a problem because of type erasure, a restriction of the JVM. At runtime, a List object knows it is a List, but not of what kind of element.

Alas, an obscure, and experimental, feature of Scala let you get around that. It’s the Manifests. A Manifest is class whose instances are objects representing types. Since these instances are objects, you can pass them around, store them, and generally call methods on them. With the support of implicit parameters, it becomes a very powerful tool:

object Registry {
import scala.reflect.Manifest

private var _map= Map.empty[Any,(Manifest[_], Any)]

def register[T](name: Any, item: T)(implicit m: Manifest[T]) {
_map = _map(name) = (m, item)
}

def get[T](key:Any)(implicit m : Manifest[T]): Option[T] = {
val o = _map.get(key)

o match {
case Some((om: Manifest[_], s : Any)) =>
if(om <:< m) Some(s.asInstanceOf[T]) else None
case _ => None
}
}
}

scala> Registry.register('a, List(1,2,3))

scala> Registry.get[List[Int]]('a)
res6: Option[List[Int]] = Some(List(1, 2, 3))

scala> Registry.get[List[String]]('a)
res7: Option[List[String]] = None

Above, Manifest is being passed as an implicit parameter – defined inside scala.reflect.Manifest – based on the type parameter T, and stored along the value. When you put a value in the register, it is not necessary to specify its type, as this gets inferred.

When you take a registry out, though, you specify the type the type you want (inference won’t help you here), and, if the key does not exist or does not store the desired type, it will return None. If a key exists and the type is correct, it will return Some(value).

Note that the operator <:< tests for subclassing. That is, the desired type m must be a superclass of the stored type om.

3 comments: