Showing posts with label type erasure. Show all posts
Showing posts with label type erasure. Show all posts

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].

Thursday, 18 March 2010

Type Frustrations II : Return of the Erasure

Work is currently sucking up every single scrap of energy I have. Work does this. Sweet, sweet release is on the distant horizon though. In order to not go completely insane, I have been continuing to fiddle with my own stuff, and here's another irritated braindump:

This seems interesting. Where by 'interesting' I mean 'very frustrating'.

I found a lovely little implementation of the simply-typed lambda calculus in the paper "Matching Objects With Patterns". This looked like an interesting way to approach the storage of heterogeneous data for game entities, so here's the bare bones in this context:

case class Var[A]( name: String )

  abstract class Env {
    def apply[A]( v: Var[A] ) : 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)
      }
    }
  }

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

  //use it like this:
  val entity = Empty.extend(Var[Int]("health"),95).extend(Var[String]("desc"),"a wild fnord")
  val entity_desc = entity(Var[String]("desc"))
  


This is easily fiddled with to remove a lot of the boilerplate when extending the Env, but anyway...

It's a little bit odd. The slightly strange v.type is what got me excited; this is the singleton type of v, in Scala parlance. This means it's a type unique to v, equal to another singleton type only if the objects compare equal according to the method eq.

Because Var[A] is a case class (and a very simple one at that), this means that v.type == w.type if the constructor arguments are the same and - my assumption - if the type parameters of the two Vars are also the same. After all, Var[Int] clearly has a different type to Var[String].

Doom.

Enter type erasure. At runtime, all those Var[Int] and Var[String] and Var[Pony] are just plain Var. Singleton types for these now only depend on the constructor arguments, which means stuff like this:

//this should produce a "not found" error:
entity(Var[String]("health"))

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String


This becomes even more horrible if the code is extended to return an Option, so that rather than producing an error on accessing an undefined variable we simply return None. In this case, the above code returns what appears to be a perfectly valid Some[String], right up until you try to access the contained 'string' and hit the same cast exception but in an entirely arbitrary point in the code, potentially miles from the source of error.

It's so very frustrating that this elegant code doesn't work, simply because a little chunk of information is not present at runtime. Type erasure; it really is the most annoying thing about working with Scala, and I bump into it far more than I would like.

PS: Picked up Clojure again. Maybe I'm completely insane (I am), but I'm tempted to write the game as a sever/client app, with the game logic entirely in Clojure and using Scala for the rendering/IO. That's a lot of extra protocols to implement, and it still has problems, but... this is exactly the kind of thing that is a non-issue in a Lisp.

PPS: I made a half-hearted stab at implementing the Env in Haskell. Yeah, the syntax has entirely leaked out of my head after a mere few months of disuse. It wasn't pretty.

Friday, 16 January 2009

Type Frustrations

Warning: this entry is largely a rant about my own ignorance.

I should have remembered this, but there's a big annoying thing that crops up whenever I try to get overly-generic in Scala.

Type erasure.

My understanding of this is that it's a pain in the arse. It has something to do with brutally discarding useful type information at runtime, such that List[Int] has the type List (or possibly List[Object]). Never mind that List is not even a type, it's a type constructor, some combination of the JVM and the writhing tentacles of an entire herd of shoggoths1 renders it a nigh-useless blob. Dark magic makes it work perfectly when all manipulations are done at compile time, but just try pattern matching or reflection with it...

What this means is that the following code will not compile:

//NB: Will not compile, type of [A] is unchecked due to erasure
def propTest( p: Property ) = p match {
case li:ListProperty[Int] => println( li.value.toString )
case lf:ListProperty[Float] => println( lf.value.mkString(":") )
case _ => println( "unknown property: "+p.toString )
}


Of course, this non-compilation is slightly less annoying than when you only try to pattern-match against a single instance of a parametrised type. In this case the compiler will emit a warning, but still compile to (apparently) working bytecode. If we dispense with the ListProperty[Float] case, the above will compile, li will match any ListProperty, and ClassCastExceptions or similar evil will almost certainly ensue.

Ignoring compilation warnings is (as we all know) incredibly dumb, and re-running compilation with the -unchecked flag will produce more helpful messages about what exactly is completely broken. It's still somewhat vexing that this compiles at all, when the chances of it doing anything useful are effectively zero.

I guess I can't complain overmuch, pattern matching against arbitrary type constructors appears to be something Haskell chokes on too, although with less smelly default behaviour and a different reason. I'm not sure how much of this is down to my downright shoddy understanding of type systems in both languages, so I'll be fiddling with this a lot more. In the interim, without some method for defining simple container properties, including type checking and pattern matching against them, the 'property bag' model appears dead in the water for my chosen implementation language.

As I said before: arse.

1What the hell is the plural of 'shoggoth'? For that matter, what's the collective noun for 'em?