Yesterday my wife took the kids with her on an overnight trip to Ann Arbor so I've had a bit of extra quiet time. How am I making use of this bounty? Getting on with some minor home repairs? Cleaning my office from top to bottom? Er, no... porting the game logic I've written so far in Objective-C back to Dylan, so that I can do some more thinking about it.
So after a phone job interview yesterday (which went well, I thought -- I'm optimistic about this possibility!) I started working on this task, and then about twelve hours later, around 2 a.m., I had the basic setup and population of the game board working. It's embarrassing to admit how long it took. I started on my Mac, and when I began encountering constant runtime errors switched over to my Ubuntu box, thinking that the Mac version of Open Dylan might just be broken (it isn't; I got the identical behavior on the Linux build). I finally figured out workarounds -- it's funny how taking a break clears my head far better than pressing on ever does -- then read a little Gene Wolfe (I'm working my way through In Green's Jungles, one of his books I've repeatedly tried and failed to finish), and fell asleep with no children in the bed to kick or otherwise interfere with a good night's sleep. I'm back up this morning, had a bath, and I'm drinking a large coffee with soy creamer and stevia and trying to hold off on a lunch break until I have some more done. It's about 10 a.m. and I'm expecting my family back in about six hours, so the race is on!
This has taken far longer than I hoped; I lost quite a bit of time stumbling across things in Open Dylan that still seem just plain broken. I had to start working on a smaller and smaller program to figure out exactly what was broken. These things I've flagged in comments, as places where, basically, I wish Dylan worked a certain way, and it doesn't. I may just be asking for something that doesn't quite match the original spec or isn't quite possible, but I'll share those with the Dylan Hackers team and see if it seems like I can help with them. The biggest thing that was broken, though, was me -- my brain, that is -- since it's been a long time since I've worked with Dylan's type-union and singleton pseudo-classes and I had forgotten the details. The compiler was not a big help with this, since it is such a dynamic language and leaves an awful lot of things to the runtime to figure out, which it does by throwing an error message that may or may not help much. The documentation is a bit scanty, but it does contain everything you need to know, if you re-read and squint at the scanty examples that are out there hard enough.
The good news is that the port is working and I'd like to share it. Dylan is still up there with Scheme (and now Haskell) as one of my favorite languages for designing programs -- yes, even though Dylan is quite old as languages go. I like to see what it can do especially with generic functions and its sophisticated model for object-oriented dispatch. I've been a little stymied as to how to express the design best in Objective-C. If it was a complicated game design, I wouldn't feel bad about having a program that looked complex. But it's really an elegantly simple game, and so I feel like the implementation should reflect that. My Objective-C implementation has been feeling more and more bloated and pointlessly complex, although it works, so my thought was to get it down to a simple implementation that takes full advantage of Dylan's object-oriented programming features, largely borrowed from CLOS, and then port that back to Objective-C, adding whatever minimalist support is needed to fake up some of the features that Dylan gives me that Objective-C doesn't have. This might be by way of also writing a Haskell or Scala implementation later, for yet more learning and language comparison, although really what I should focus on is getting the iOS GUI up and working so that I have something to show people.
Anyway, I've got a Dylan program that plays the Polar game, using singletons to represent tile types, and methods dispatched on singletons to handle specific kinds of collisions. The classes -- which are empty, pretty much used only for their usefulness as types, for driving dispatch -- are like so:
In Dylan you can create some instances, and create something called a type-union, which is something that is a type, I think, but not a class. You can use it to define a slot type or a parameter type. But you can't make one:
define constant $the-bomb = make(
); define constant $the-empty = make( ); define constant $the-heart = make( ); define constant $the-house = make( ); define constant $the-ice-block = make( ); define constant $the-mountain = make( ); define constant $the-tree = make( ); define constant = type-union( singleton( $the-bomb ), singleton( $the-empty ), singleton( $the-heart ), singleton( $the-house ), singleton( $the-ice-block ), singleton( $the-mountain ), singleton( $the-tree ), singleton( #f ) );
And eventually dispatch on singletons -- meaning that a given method will be called with it is called with references to the exact objects that you specify:
define method collide( model :: <model>, dir :: <dir>, heart-pos :: <pos>, heart-tile == $the-heart, house-pos :: <pos>, house-tile == $the-house ) format-out( "collide: $the-heart / $the-house\n" ); setTileAtPos( model, heart-pos, $the-empty ); model.decrementHeartCount(); end;
That gives you an idea of how some of the code in the Dylan program is organized. I have it mostly working, however, I'm not going to present the full code quite yet because I have a crashing bug, and I haven't yet been able to figure out if it is a dumb mistake on my part or a compiler or runtime bug in Open Dylan. I've also asked the Dylan hackers to take a look at my design and see what they think -- if they can find, as I put it, "a simpler design struggling to get out." Which is always the challenge, when trying to write not just functional, but model code, isn't it?