Courtesy of Hacker News, this morning I stumbled upon a blog post mentioning :pre and :post assertions, a new feature in version 1.1 of Clojure. Given the rather messy nature of several functions in Gajure (my toy genetic algorithm framework), it seemed to me that I had an ideal opportunity to make use of this new functionality.
For instance, although the function run-ga only takes two parameters, both of them are hashes. Naturally, each must contain a few keys for the genetic algorithm to run properly. Using preconditions, it’s easy to ensure that the function’s parameters contain these required keys. For instance:
(defn keys-not-nil [lst hash]
(reduce #(and %1 %2) (map #(not (nil? (hash %))) lst)))
This checks each key in a list of keys, and returns false if one or more of these keys maps to a value of nil. Obviously, I could go further here, but checking for non-nil values seemed a reasonable (and easy) generalization (e.g. if :init-fn or :mut-r map to nil, then the ga-run function will have a problem). Now, making use of the :pre tag.
(defn run-ga
[func-map setting-map]
{:pre [(and
(keys-not-nil
(list :init-fn :fit-fn :mut-fn :sel-fn :cross-fn)
func-map)
(keys-not-nil
(list :pop-sz :gen :children :mut-r)
setting-map))]}
... actual algorithm ...)
So I can now enforce the (arbitrary) required keys, and return assertion errors on some improper uses of ga-run. For a more specific example, look to the algorithm’s mutation function.
(defn generic-mutation
"Randomly mutates lists with elements from other lists in the population."
[list prob]
{:pre [(and (>= prob 1) (<= prob 100))]
:post [(list? %)]}
(map
(fn [s-list]
(map
(fn [test]
(if (> prob (rand-int 100))
(let [r-s (rand-int (count list))
r-t (rand-int (count (nth list r-s)))]
(nth (nth list r-s)
r-t))
test))
s-list))
list))
Here, the :pre tag ensures that the value of prob is between one and one hundred. Similarly, :post asserts that the function returns a list (although I haven’t mentioned it, the :post tag operates much like :pre, using a function’s return value within its assertion). So in spite of my trivial examples, perhaps you can begin to appreciate how these tags can be used to create safer and more predictable code.
How I develop on OSX
It recently occurred to me that I’m dumb. I certainly don’t mean this in any pejorative sense (after all, that would be abrasive to the ego), but rather I would suggest it as regards a behavioral pattern that I tend to follow. Roughly, said pattern goes like this:
For instance, a few days ago I wanted to put together a small rails application. There was nothing earth shattering in this; I simply wanted to get a quick prototype up and running. So I update my gems, do a quick `rails prototype/` command, and boot up the empty app. It fails.
Ok, so I do the natural thing; I google the error message or whatnot; I search for a few minutes. Well, it has something to do with Snow Leopard, that much becomes clear. This begs the question: does any bug ever not have something to do with Snow Leopard? But I won’t adress that here. Anyhow, there is an obscure solution, under which I’d have to edit manually my rails installation — something like that, anyway. But I’m not going to do that. I try some earlier versions of rails and it’s dependancies — they fail for some other reasons.
I’m sure that I could have figured it out, had I dug around for a while. I might have, with work, discovered the vagaries behind why the same programs, with the same (nominally) installed dependancies exhibited opposite behaviors on different machines. But no, by next month I’d probably just have a new and similar problem. So I do just what any irrational fellow ought to; I decide to install a virtual Arch distro (my favorite) with VMware fusion, mount the machine as a disk via ssh, and develop from there. At least in Arch, I will know exactly what is installed, where it is, and how it ought to be working. Basically, I eliminate the free variable of imprecise ignorance regarding everything that’s on the machine, and whether I’ve hacked it up manually. Of course, god forbid that I give up TextMate and actually code directly on a linux machine. Emacs is awesome for my more lispy projects, but I like TM for rails.
To be clear, the blame here is almost entirely on me. However, given the recent announcement of the iPad, I feel that it’s become the custom to lay down some hate. So here goes: Apple, you ought to implement a decent package management scheme. Some inconsistency with something I had installed (or worse, you put there) was messing things up. Sure, one might say that I use pacman (or yum, or insert-reader-favorite) as a crutch, and I’m fine with that. In particular, said management helps prevent people like me — overly exuberant installers and occasional hackers of who-knows-what – from messing up systems.
So on the one hand, I get what I want. Things tend to work on Arch, and updating is only a `pacman -Syu` away. On the other hand — yeah, I’m dumb.