Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class type as key in map in Scala

My game has

class Enemy

who's AI/functionality I can change with

trait Moving
trait VerticalMover extends Moving
trait RandomMover extends Moving

and so on. Now I need to fetch preloaded stuff based on trait. What I would like to do is have a Map that accepts all traits that extend Moving as keys and then some EnemyContainer as value that would have trait related content preloaded.

But how do I define such a Map and how do format my .get() to get the container by an instance of some Enemy. Something like:

val myEnemy = new Enemy with RandomMover 
val myDetails:EnemyContainer = enemyDetailsStore.get(myEnemy.getClass)
like image 901
vertti Avatar asked Sep 07 '11 14:09

vertti


2 Answers

Maybe you could wrap a Map[Manifest, Any] ensuring that the values corresponds to the manifest keys.

Possible sketch of that. First a little helper

class Typed[A](value: A)(implicit val key: Manifest[A]) {
  def toPair: (Manifest[_], Any) = (key, value)
}
object Typed {
  implicit def toTyped[A: Manifest](a: A) = new Typed(a)
  implicit def toTypable[A](a: A) = new {
    def typedAs[T >: A : Manifest] = new Typed[T](a)(manifest[T])
  }
}

then the wrapper itself (which is not a map)

class TypedMap private(val inner: Map[Manifest[_], Any]) {
  def +[A](t: Typed[A]) = new TypedMap(inner + t.toPair)
  def +[A : Manifest](a: A) = new TypedMap(inner + (manifest[A] -> a))
  def -[A : Manifest]() = new TypedMap(inner - manifest[A])
  def apply[A  : Manifest]: A = inner(manifest[A]).asInstanceOf[A]
  def get[A : Manifest]: Option[A] = inner.get(manifest[A]).map(_.asInstanceOf[A])
  override def toString = inner.toString
  override def equals(other: Any) = other match {
    case that: TypedMap => this.inner == that.inner
    case _ => false
  }
  override def hashCode = inner.hashCode
}

object TypedMap {
  val empty = new TypedMap(Map())
  def apply(items: Typed[_]*) = new TypedMap(Map(items.map(_.toPair) : _*))
}

With that you can do

import Typed._
val repository = TypedMap("foo", 12, "bar".typedAs[Any])

repository: TypedMap = Map(java.lang.String -> foo, Int -> 12, Any -> bar)

You retrieve elements with

repository[String] // returns "foo"
repository.get[Any] // returns Some("bar")

I think the private constructor should ensure that the _asInstanceOf is safe. inner may be left public, as it is immutable. This way, the rich interface of Map will be available, but unfortunately, not to create another TypedMap.

like image 191
Didier Dupont Avatar answered Oct 03 '22 09:10

Didier Dupont


Well, I assume that your enemy details store is of type Map[Class[_ <: Moving], EnemyDetails]. I suspect that something like:

//gives a Map[Class[_ <: Moving], EnemyDetails] for all matching keys
enemyDetailsStore.filterKeys(_ isInstance myEnemy) 

Or:

//Iterable[EnemyDetails]
enemyDetailsStore collect { case (c, d) if c isInstance myEnemy => d }

Or even just:

//Option[EnemyDetails]
enemyDetailsStore collectFirst { case (c, d) if c isInstance myEnemy => d }

Will do for you. The only "issue" with this code is that it's O(N), in that it requires a traversal of the map, rather than a simple lookup, which would be O(1), or O(log N)

like image 43
oxbow_lakes Avatar answered Oct 03 '22 08:10

oxbow_lakes