4

Let's say that for some good reason I want a generic HashMap that contains all types of objects. I also want to push any unsightly instanceof-like type checks into the data structure. To this end, a method like getAs[T <: Any](key: String): Option[T] would be nice to have.

class State extends HashMap[String, Any] {

  override def +[T >: Any](elem: (String, T)): State = super.+(elem)
  override def -(key: String): State = super.-(key)

  def getAs[T](key: String): Option[T] = {
    super.get(key) match {
      case s: Some[T] => s
      case _ => None
    }
  }

}
object State extends HashMap[String, Any] {
  override def empty: State = super.empty
}

I also have the following implicit conversion defined in the package object:

implicit def fromHashMap(m: HashMap[String, Any]): State = m.asInstanceOf[State]

When I compile the above code I get the following type erasure warning:

State.scala:10: warning: non variable type-argument T in type pattern Some[T] is
unchecked since it is eliminated by erasure
case s: Some[T] => s
        ^

This is unfortunate, since the entire purpose of that statement if to check the type!

In this case, do I have an option other than resorting to the experimental Manifest features? Moreover, is there a better basic approach to accomplish this?

Edit:

I got this working with using Manifests. I was helped along greatly by this article on Stackoverflow. However, I am still curious if there is a cleaner way anyone can recommend.

Double Edit:

Here is the current version of this. Manifests solved my immediate issues. However, a major change I made was to make the State class a wrapper for a Map. In doing so, I lost the advantages of inheritance (i.e. now I have to explicitly expose each map method I need a la keySet below.

class State(
  map: HashMap[String, (Manifest[_], Any)] = 
    scala.collection.immutable.HashMap.empty[String, (Manifest[_], Any)]
) extends java.io.Serializable {

  def +[T <: Any](elem: (String, T))(implicit m: Manifest[T]): State =
    State(map.+((elem._1, (m, elem._2))))

  def -(key: String): State = State(map.-(key))

  def keySet = map.keySet

  def getAs[T](key: String)(implicit m : Manifest[T]): Option[T] = {
    map.get(key) match {
      case Some((om: Manifest[_], o: Any)) =>
        if (om <:< m) Some(o.asInstanceOf[T]) else None
      case _ => None
    }
  }

}

object State {
  def apply() = new State()
  def apply(map: HashMap[String, (Manifest[_], Any)]) = new State(map)
  def empty = State()
}

Thanks to everyone who has looked at this so far.

Update for Scala 2.10:

See the current implementation of State using ClassTag and friends here on GitHub. I plan to update it to use TypeTags once TypeCreators are serializable.

Community
  • 1
  • 1
Connor Doyle
  • 1,812
  • 14
  • 22
  • A minor mistake in your code, matching on Some[T] would likely not work even without erasure. In all likelihood, get in any Map[A,B] impl would return a new Some[B], so in your case, it would be Some[Any] whatever the actual type of the value inside. So without type erasure, the proper clause would be case Some(t: T) => Some(t). – Didier Dupont Aug 26 '11 at 06:51
  • Looks like the github link is broken – Joe J Jun 10 '13 at 01:06

1 Answers1

2

Note that while

case s: Some[T]

Doesn't work because Some's type parameter is erased, this is different:

case s @ Some(_: T)

In this case, it doesn't work because T is a type parameter itself. While this does not make a difference in your case (no, there's no way other than manifests), consider the following:

case s: Some[Int]      // erased
case s @ Some(_: Int)  // not erased
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 1
    Could you point me toward some documentation on the `@` operator? I have not come across that yet. – Connor Doyle Aug 28 '11 at 04:25
  • what would `case Some(_ : T)` do? – Raphael Aug 28 '11 at 13:05
  • @ConnorDoyle Check [this question](http://stackoverflow.com/questions/2359014/scala-operator). – Daniel C. Sobral Aug 29 '11 at 19:02
  • 1
    @Raphael It will verify that the value can be extracted with `Some.unapply` (or `Some.unapplySeq`, if it existed), and, if so, check the class of the extracted value against `T`. If `T` is erased, it will just compare it against `Object`, which is always true (even `AnyVal` subtypes get boxed). – Daniel C. Sobral Aug 29 '11 at 19:04
  • Thanks. I think this is curious: type parameter is not erased if the matched expression is assigned a name, but is if not. – Raphael Aug 30 '11 at 08:26
  • @Raphael Type parameter is always checked. When you do `S @ Some(_: T)` you are not checking that `s` is of type `Some[T]`. You are checking that `s` is a `Some` whose content has _type_ `T` (not type parameter). – Daniel C. Sobral Aug 30 '11 at 14:37