It's not about technology for its own sake. It's about being able to implement your ideas.
Key bits and pieces from the functional programming world have, perhaps surprisingly, been assimilated into the everyday whole of development: single-assignment "variables," closures, maps and folds, immutable strings, the cavalier creation and immediate disregarding of complex structures.
The next step, and you really need this one if you want to stick to the gospel of referential transparency that dominated the early push toward FP, is immutable everything. You can create objects and data structures, but you can't modify them--ever. And this is a hard problem, or at least one that requires thinking about programs in a different way. Working that out is what drove many of my early blog entries, like Functional Programming Doesn't Work (and what to do about it) from 2009.
Across the board immutability may seem a ridiculous restriction, but on-the-fly modifying of data is a dangerous thing. If you have a time-sliced simulation or a game, and you change the internals of an object mid-frame, then you have to ask "At what point in the frame was this change made?" Some parts of the game may have looked at the original version, while others looked at it after the change. Now magnify the potential for crossed-wires by all of the destructive rewriting going on during a typical frame, and it's a difficult problem. Wouldn't it be better to have the core data be invariant during the frame, so you know that no matter when you look at it, it's the same?
I wrote a number of smaller games in Erlang, plus one big one, exploring this, and I finally had a realization: keeping the whole of a game or simulation frame purely functional is entirely unrelated to the functional programming in the small that gets so much attention. In fact, it has nothing to do with functional programming languages as all. That means it isn't about maps or folds or lambdas or even avoiding destructive-operation languages like C or C++.
The overall approach to non-destructive simulations is to keep track of changes from one frame to the next, without making any changes mid-frame. At the very end of the frame when the deltas from the current to the next have been collected, then and only then apply those changes to the core state. You can make this work in, say, purely functional Erlang, but it's tedious, and a bit of a house of cards, with changes threaded throughout the code and continually passed back up the call-chain.
Here's an alternate way of thinking about this. Instead of modifying or returning data, print the modification you want to make. I mean really print it with printf
or its equivalent. If you move a sprite in the X direction, print this:
sprite 156: inc x,1.5
Of course you likely wouldn't use text, but it's easier to visualize than a binary format or a list of values appended to an internal buffer. How is this different than passing changes back up the line? It's direct, and there's now one place for collecting all changes to the frame. Run the frame logic, look at the list of changes, apply those changes back to the game, repeat. Never change the core data mid-frame, ever.
As with most realizations, I can see some people calling this out as obvious, but it's something I never considered until I stopped thinking about purely functional languages as a requirement for writing purely functional software. And in most other languages it's just so easy to overwrite a field in a data structure; there's a built-in operator and everything. Not going for that as the first solution for everything is the hard part.
(If you liked this you might enjoy Turning Your Code Inside-Out.)
permalink May 3, 2015
I'm James Hague, a recovering programmer who has been designing video games since the 1980s. Programming Without Being Obsessed With Programming and Organizational Skills Beat Algorithmic Wizardry are good starting points. For the older stuff, try the 2012 Retrospective.
Where are the comments?