codersnotes

Idea-driven development June 6th, 2015

Somebody recently asked me why I like the book Practical Common Lisp so much. Well, I probably can't do a very good job of explaining it, but here goes. I've often found Lisp to be like Latin - you'll never use it, but just learning a little about it teaches you new things about your own language. I'm not a Lisp programmer, but reading that book changed how I program in C++.

Let's imagine that you want to have weapons in your game, each having perhaps a distance range and a damage amount. If you were writing it in C++, your best option might be to try and load that data out of an XML/JSON file. But for the sake of argument, let's say you wanted to try and declare it in the game itself.

So a programmer's general approach is often to first declare some sort of data structure:
struct WeaponInfo {
    const char *name;
    float range;
    float damage;
};
And then you think, "well how do I fill these in?". So then maybe you write an init function like this:
WeaponInfo *shotgun, *sharpstick, *bazooka;


void SetupWeapons() { shotgun = new WeaponInfo; shotgun->name = "shotgun"; shotgun->range = 10.0; shotgun->damage = 4.0; sharpstick = new WeaponInfo; sharpstick->name = "sharpstick"; sharpstick->range = 1.0; sharpstick->damage = 1.0; bazooka = new WeaponInfo; bazooka->name = "bazooka"; bazooka->range = 40.0; bazooka->damage = 12.0; }
Well that seems OK so far. But what if later on, you decide that you actually need a list of all the weapons. So now you go through and add code to make that list:
    weaponsList.push_back(shotgun);
    weaponsList.push_back(sharpstick);
    weaponsList.push_back(bazooka);
Now to many programmers, and definitely me a few years ago, this would have seemed perhaps a totally normal way to be doing things. Maybe you don't see the problem here. Well, I'll highlight it for you:



Duplication! You've taken one idea, and repeated it out three times (one for each weapon). And then you've taken another idea (the name of the weapon) and repeated that 7 times within each case.

Now there are better ways to write this code in C++. There really are. But often you won't think to use them because you're writing your code backwards. You started by defining what a weapon needs to be, then writing code to fill it in, then finally using that code to create your weapons list.

So how would the Lisp programmer approach this problem?: Well they'd start from the end. The first thing you do is to write what you want to say:
(define-weapon shotgun     10.0  4.0)
(define-weapon sharp-stick  1.0  1.0)
(define-weapon bazooka     40.0 12.0)
That's it! That's all the code we need to declare our intentions. It's just as compact as, for example, a JSON document.

What remains now is to implement define-weapon in order to actually make it do something. In Lisp you have a variety of options as to how exactly you'd hook it up. You'd probably make it a macro, which could then declare new global variables and initalize them. You'd probably then come up with a WeaponInfo class to hold it, with some properties to fill in. But the key point is that you do that afterwards, and the code declaring the weapons doesn't know anything about it.

And later on, if you want to change the implementation of define-weapon to something totally different, for example, to build serialization tables, you can just rewrite it and the caller doesn't need to change anything.

Of course, C++ has macros too, so you could do something similar there perhaps:
DEFINE_WEAPON(shotgun,    10.0,  4.0);
DEFINE_WEAPON(sharp-stick, 1.0,  1.0);
DEFINE_WEAPON(bazooka,    40.0, 12.0);
Which does the job. But the trouble with C/C++ macros is that they weren't designed for this kind of thing, so it's generally inadvisable to use them quite like this. How do you append those items to a global list in a C++ macro? It might be possible, but it sure as hell won't be pretty. Whereas in Lisp, you just append it the same way you'd use any regular list.

I want to make one thing clear - this isn't a debate of whether it's a good idea to use macros, or how to initialize data. There are almost certainly better ways of initializing a list of weapons in C++, and I have no doubt that someone will reply listing some, thereby completely missing the point of the article. It's simply about what you want to say in your program, and the approach you use to go about it. A program should aspire to be a way of clearly expressing ideas, and the idea we want to express here is that we have a shotgun with some properties. We shouldn't allow the busywork needed to implement those ideas to cloud the ideas themselves.

Fred Brooks, in his classic book "The Mythical Man Month" talks about accidental complexity vs. essential complexity. Essential complexity is what is actually required to solve a problem. Accidental complexity is what we put in ourselves while trying to solve it. While the goal is to try and reduce accidental complexity, you can never get rid of it all. But what you can do is to keep those two as separate as possible in your codebase.

Ideas first, details later.

 

Written by Richard Mitton,

software engineer and travelling wizard.

Follow me on twitter: http://twitter.com/grumpygiant