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.
One Trackback
[...] This post was mentioned on Twitter by ajlopez, :=a name. :=a name said: Clojure :pre and :post http://blog.ethanjfast.com/2009/12/clojure-pre-and-post/ [...]