I'm James Hague, a recovering programmer who has been designing video games since the 1980s. This is Why You Spent All that Time Learning to Program and The Pure Tech Side is the Dark Side are good places to start.
Where are the comments?
I know I've covered this before. I am repeating myself. But it was woven into various other topics, never stated outright:
Some programming languages, especially among those which haven't gained great popularity, are puzzles.
That's not to be confused with "programming in general is a puzzle." There's always a certain amount of thought that goes into understanding a problem and deciding upon an approach to solving it. But if it takes focused thought to phrase that solution into working code, you go down one path then back up, then give up, then try something completely different--then you're almost certainly using a puzzle language.
These are puzzle languages:
And these are not:
In Forth, the puzzle is how to simplify a problem so that it can be mapped cleanly to the stack. In Haskell and Erlang, the puzzle is how to manage with single assignment and without being able to reach up and out of the current environment. In J the puzzle is how to phrase code so that it operates on large chunks of data at once.
Compare this to, say, Python. I can usually bang out a solution to just about anything in Python. I update locals and add globals and modify arrays and get working code. Then I go back and clean it up and usually end up with something simpler. In Erlang, as much as I want to deny it, I usually pick a direction, then realize I'm digging myself into a hole, so I scrap it and start over, and sometimes when I end up with a working solution it feels too fragile, something that wouldn't survive minor changes in the problem description. (Clearly this doesn't apply to easy algorithms or simple transformations of data.)
A critical element of puzzle languages is providing an escape, a way to admit that the pretty solution is elusive, and it's time to get working code regardless of aesthetics. It's interesting that these escapes tend to have a stigma; they induce a feeling of doing something wrong; they're guaranteed to result in pedantic lecturing if mentioned in a forum.
In Forth, an easy pressure value when the stack gets too busy is to use local variables. Local variables have been historically deemed unclean by a large segment of the Forth community (although it's amazing how easy some Forth problems are if you use locals). There's a peculiar angst involved in avoiding locals, even if they clearly make code simpler. Locals aside, there's always the escape of using some additional global variables instead of stack juggling, which has a similarly bad reputation (even though everyone still does it).
In Erlang, ETS tables and the process dictionary are two obvious escapes. And as expected, any mention of the process dictionary always includes the standard parental warning about the dangers of playing darts or standing there with the refrigerator door open. It is handy, as shown by the standard library random number generator (which stores a three element tuple under the name
random_seed), and Wings3D (which uses the process dictionary to keep track of GUI state).
A more interesting escape in Erlang is the process. A process is commonly thought of as a mechanism of concurrency, but that need not be the case. It's easy to make an infinite loop by having a tail recursive function. Parameters in such a loop can be--if you dig into the implementation a bit--directly modified, providing a safe and interesting blurring of functional and imperative code. Imagine taking such a function and spawning it into its own process. Each process captures a bit of relevant data in a small, endlessly recursive loop. Imagine dozens or hundreds of these processes, each spinning away, holding onto important state data. Erlang string theory, if you will.
I wouldn't want to break a program into hundreds of processes simply to capture state, but usually there are some important bits which are used and updated across a project. Pulling these out of the purely functional world can be enough of a relief from growing complexity that the rest of the code can remain pure.
But there's still that stigma of doing something dirty. Back before the Norton name became associated with anti-virus products, when MS-DOS was ubiquitous, Peter Norton authored the standard book on programming IBM PCs. In a discussion of the MS-DOS interrupts for displaying characters and moving the cursor, he strongly advised that programmers not access video memory directly, but use the provided services instead. (The theory being that the MS-DOS interrupts would remain compatible on future hardware.) Of course almost every application and game would not have been possible had developers taken Peter's advice to heart. Learning to write directly to video memory was practically a cottage industry until Windows 95 finally ended the DOS era.
Sometimes advice is too idealistic to follow.