Class type as key in map in Scala

2020-07-07 03:18发布

问题:

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)

回答1:

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)



回答2:

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.