programming in the
twenty-first century

It's not about technology for its own sake. It's about being able to implement your ideas.

Evolution of an Erlang Programming Style

I first learned Erlang in 1999, and it's still my go-to language for personal projects and tools. The popular criticisms--semicolons, commas, and dynamic typing--have been irrelevant, but the techniques and features I use have changed over the years. Here's a look at how and why my Erlang programming style has evolved.

I came to Erlang after five years of low-level coding for video games, so I was concerned about the language being interpreted and the overhead of functional programming. One of the reasons I went with Erlang is that there's an easy correspondence between source code and the BEAM virtual machine. Even more than that, there's a subset of Erlang that results in optimal code. If a function makes only tail calls and calls to functions written in C, then parameters stay in fixed registers even between functions. What looks like a lot of parameter pushing and popping turns into destructive register updates. This is one of the first things I wrote about here, back in 2007.

It's curious in retrospect, writing in that sort of functional assembly language. I stopped thinking about it once BEAM performance, for real problems, turned out to much better than I expected. That decision was cemented by several rounds of hardware upgrades.

The tail-recursive list building pattern, with an accumulator and a lists:reverse at the end, worked well with that primitive style, and it's a common functional idiom. Now I tend to use a more straightforward recursive call in the right hand side of the list constructor. The whole "build it backward then reverse" idea feels clunky.

For a small project I tried composing programs from higher-level functions (map, filter, foldl, zip) as much as possible, but it ended up being more code and harder to follow than writing out the "loops" in straight Erlang. Some that is awkward syntax (including remembering parameter order), but there are enough cases where foldl isn't exactly right--such as accumulating a list and counting something at the same time--that a raw Erlang function is easier.

List comprehensions, though, I use all the time. Here the syntax makes all the difference, and there's no order of parameters to remember. I even do clearly inefficient things like:

lists:sum([X || {_,_,X} <- List]).

because it's simpler than foldl.

I use funs--lambdas--often, but not to pass to functions like map. They're to simplify code by reducing the number of parameters that need to be passed around. They're also handy for returning a more structured type, a sort of simple object, again to hide unneccessary details.

Early on I was also concerned about the cost of communicating with external programs. The obvious method was to use ports (essentially bidirectional pipes), but the benchmarks under late-1990s Windows were not good. Instead I used linked-in drivers, which were harder to get right and could easy crash the emulator. Now I don't even think about it: it's ports for everything. I rewrote a 2D action game for OS X with the graphics and user input in an external program and the main game logic in Erlang. The Erlang code spawns the game "driver," and they communicate via a binary protocol. Even at 60fps, performance is not an issue.

permalink July 11, 2016

archives

twitter / mail

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?