Saturday, 20 December 2008

Improving my Clojure style

P22 on my list of problems seems simple, "Create a list containing all integers within a given range.". My first naive (a polite way of saying crap!) implementation is shown below:

(defn my-range-crap [start end]
((fn [x y accum]
(if (= x y)
(concat accum (list x))
(recur (inc x) y (concat accum (list x))))) start end nil))

The pattern (concat accum (list x)) is very ugly. There's a much simpler way of doing that using conj. From the documentation.

(conj coll item)
Conj[oin]. Returns a new collection with the item 'added'. (conj nil item) returns (item). The 'addition' may happen at different 'places' depending on the concrete type.

Much better, so now I can do:

(defn my-range-not-quite-as-crap [start end]
((fn [x y accum]
(if (= x y)
(conj accum x)
(recur (inc x) y (conj accum x)))) start end nil))

Incidentally, this fixes another issue. If I'm using concat then (my-range-crap 1 50000) blows the stack. Not good. Using conj fixes the problem.

Talking on #Clojure, I got a much better solution from kotarak + poiuyt

(defn much-better-range
[start end]
(when (< start end)
(lazy-cons start (much-better-range (inc start) end))))

Much more concise!

Using lazy-cons means the recursion can be explicit and that you get rid of the problems with stack overflow (it only evaluates when you need it).