Revisiting "Purely Functional Retrogames"

I wrote the Purely Functional Retrogames series as an experiment. There's been so much empty talk about how functional languages are as good or better than imperative languages--yet very little to back that up. Doubly so in regard to interactive applications. I'd bet there are more people learning to program the Atari 2600 hardware than writing games in Haskell.

For people who only read the beginnings of articles, let me say this up front: Regurgitating the opinion that functional programming (or any technology) is superior does absolutely nothing. That's a road of endless conjecture and advocacy. One day you'll realize you've been advocating something for ten years without having any substantial experience in it. If you think a particular technology shows promise, then get in there and figure it out.

The rest of this entry is about what I learned by writing some retro-style games in Erlang.

The divide between imperative and functional languages, in terms of thinking about and writing code, is much smaller than I once thought it was. It is easy to accidentally introduce side-effects and sequencing problems into purely functional code. It is easy to write spaghetti code, where the entanglement comes from threading data through functions rather than unstructured flow of control. There's mental effort involved in avoiding these problems, just as there is when programming in any language.

Not being able to re-use names for values in functions sometimes led to awkward code. I could have said "not being able to destructively update local variables..." but that would show a lack of understanding that local "variables" are just names for things, not storage locations. For example, the initial position of an object is passed in as "Pos." I use it to create a new position, called "NewPos." Sometimes, because it was the most straightforward approach, I'd end up with another value called "NewPos2" (which I will agree is a horrible name). Then I'd find myself referring to "NewPos" when I meant "NewPos2." If the need arose to shuffle the logic around a bit, then it took care to manually rename these values without introducing errors. It would have been much nicer to create the new position and say "I'm repurposing the name 'Pos'" to refer to this new position.

The lack of a simple dictionary type was the most significant hindrance. This is where I found myself longing for the basic features of Perl, Python, Ruby--pretty much any common language. The ugliness of Erlang records has been well-documented, but syntax alone is not a reason to avoid them. The real problem is that records are brittle, exactly like structs in C. It's difficult to come up with a record that covers the needs of a dozen game entities with varying behaviors. They all have some common data, like an X/Y position, but some have special flags, some use velocities and acceleration values instead of simple motion, some need references to other entities, and so on. I could either make a big generic record that works for everything (and keep adding fields as new cases arise) or switch to another data structure, such as a property list, that doesn't allow pattern matching.

The biggest wins came from symbolic programming and consciously avoiding low-level efficiency. I spent a lot of time focused on efficient representations of entity states and other data. The more thought I put into this, the more the code, as a whole, became awkward and unreadable. Everything started to flow much more nicely when I went down the road of comical inefficiency. Why use a fixed-size record when I can use a tree? Why use bare data types when I can tag them so the code that processes them is easier to read? Why transform one value to another when I can instead return a description of how to transform the value?

Some years ago, John Carmack stated that a current PC was powerful enough to run every arcade game he played when he was growing up--some 300 or so games--at the same time. Yes, there's a large execution time penalty simply for choosing to write in Erlang instead of C, but it's nowhere near enough a factor to matter for most game logic, so use that excess to write code that's clear and beautiful.