In that game, I have units, and those units have attributes like health, attack power and so on. I wanted to write a bunch of functions to get, set, modify and reset those values. Thats four functions times seven attributes... 28 functions. Naturally, it would be crazy to spell out ALL of those accessors by hand, especially since they are almost identical to each other in many cases.
What I did is made of three parts, basically.
First, I defined a set of attributes, which will be used later.
Second, I wrote generic accessors, accepting a keyword naming the attribute to work on as first argument, and whatever else they needed as the remaining arguments. I called them set-, get-, change- and reset-.
Last, and this is the most fun part, I wrote a little tidbit of top-level code:
(doseq [stat stats, f ["get-", "set-", "change-", "reset-"]]
(eval `(def ~(symbol (str f (->> stat str rest (apply str))))(partial ~(symbol f) ~stat))))
While I didn't use defmacro explicitly, I use the macro mechanics here. I just wrote it inline. What happens here isn't very difficult to understand once you grasp the basics of macros, but I will step through it nonetheless.
The first line can basically translated as "for every possible combination of stats and those string, do the following".
eval executes the code it is given. Executing LISP code literally means just running it through eval. Also, it is a special form. But nevermind that right now.
The backtick ` is a very pleasant thing introduced in Clojure to combat common macro problems which aren't important right now, but generally it does the same as an apostrophe ', it turns the expression it precedes into a literal (when writing macros in Clojure, use backticks, never apostrophes - ALWAYS). So while (+ 2 5) evals to 7, '(+ 2 5) evals to... well, '(+ 2 5). The other one, the wave ~ (or however it is called), kind of cancels the quoting. As an example, '(+ ~(+ 1 1) 5) evaluates to '(+ 2 5).
Now it becomes fairly obvious what is happening here. We plug the attribute and function names into a generic def.
As an example, for :attack and "get-"...
`(def ~(symbol (str "get-" (->> :attack str rest (apply str))))
(partial ~(symbol "get-") :attack))))
Which then turns into...
`(def get-attack
(partial get- :attack))And that is given to eval to finally execute it.
The abominable first line happens due to my choice of using keywords - running a keyword through str returns it together WITH the colon, and I remove it with the str rest (apply str) part. The second line is fairly straightforward. This is why it was important that the name of the attribute be the first argument.
Anyways, now, whenever the file is executed (be it by loading or by... well, running it), 28 functions are generated. I can easily add more attribute accessors by adding entries to the list of attributes, I can easily change the implementations of the functions themselves without having to wade through tons of code and correcting them in several places at once (which is a really error-prone procedure). Also, should I never need to, I can add new types of accessors with a minimum of hassle. And finally - I had fun writing accessors. When was the last time that ever happened to any of you?
I'm am fairly confident I am entitled to say I did an awesome job there.
Now I should go play Guitar Hero "Fury Of The Storm" on hard before my self-esteem gets too high.
PS: I slightly modifed the generic def later. The first line turned into
`(def (->> stat str rest (apply str) (str f) symbol)
It doesn't change anything, but looks more consistent, in my opinion.
PPS: The base functions (get-, set-...) are private. Just saying.