Monday 22 March 2010

Type Frustrations III - Implicitly Manifest

Apologies for the title. It is bad.

I found this incredibly helpful, though, and set about stealing the ideas therein.

So, how's about this for a solution:

  case class Var[A]( name: String, tag: String )

  abstract class Env {
    def apply[A]( v: Var[A] ) : A
    def get[A]( v: Var[A] ) : Option[A]
    def extend[A]( v: Var[A], x: A ) : Env = new Env {
      def apply[B]( w: Var[B] ) : B = w match {
        case _ : v.type => x
        case _ => Env.this.apply(w)
      }
      def get[B]( w: Var[B] ) : Option[B] = w match {
        case _ : v.type => Some(x)
        case _ => Env.this.get(w)
      }
    }

    def apply[A]( n: String )( implicit m: scala.reflect.Manifest[A] ) : A = apply( Var[A](n, m.toString) )
    def get[A]( n: String )( implicit m: scala.reflect.Manifest[A] ) : Option[A] = get( Var[A](n, m.toString) )
    def extend[A]( n: String, x: A )( implicit m: scala.reflect.Manifest[A] ) : Env = extend[A]( Var[A](n, m.toString ), x )
  }

  object Empty extends Env {
    def apply[A]( v: Var[A] ) : A = error( "not found: "+v.name )
    def get[A]( v: Var[A] ) : Option[A] = None
  }

  //usage
  val e1 = Empty.extend("health", 95).extend("desc","wild fnord").extend("hates",List("humans","puppies"))
  val e1Health = e1[Int]("health")

  //this will be None due to type mismatch, as desired!
  val e1Hates = e1.get[List[Int]]("hates") 

The original has been extended by get, which is a slightly nicer way of handling likely-to-be-absent properties.

Manifests are strange beasts, but the above seems to work nicely. It behaves well when presented by parametrised types such as the List[String] in the example. They appear to be implicitly present without any imports, and although the code is slightly magical it's also a very clean solution to my problem.

Next task will be to make this serialise nicely, and a way to drop old vars. Currently every change to the entity will be recorded (a history method is trivial and could be useful), which is nice, but could eventually lead to very large entities. In particular, the player character will end up huge. Whilst I'm not exactly laser-like in my focus on performance here, 'potentially infinite' isn't a good size for a game object.

P.S. The very shiny backquote syntax in the match statements doesn't quite work in this case, as without the explicit use of the singleton type Scala doesn't believe that `v` can be of type Var[B].

No comments: