In "The Joy of Clojure", 2nd edition, code for quicksort is introduced on page 133 that I found hard to digest. I have rewritten it to make it clearer (at least to myself).
Here is the original:
(defn sort-parts-joc [work]
(lazy-seq
(loop [[part & parts] work]
(if-let [[pivot & xs] (seq part)]
(let [smaller? #(< % pivot)]
(recur (list*
(filter smaller? xs)
pivot
(remove smaller? xs)
parts)))
(when-let [[x & parts] parts]
(cons x (sort-parts-joc parts)))))))
Here is the rewritten code:
(defn sort-parts-explicit-lazy [work]
(lazy-seq
(loop [loopwork work]
(let [[ part & partz ] loopwork ]
(if-let [[pivot & valuez] (seq part)]
(let [ smaller? #(< % pivot)
smz (filter smaller? valuez)
lgz (remove smaller? valuez)
nxxt (list* smz pivot lgz partz) ]
(recur nxxt))
(if-let [[oldpivot & rightpartz] partz]
(cons oldpivot (sort-parts-explicit-lazy rightpartz))
[]))))))
Testing:
(require '( clojure test ))
(clojure.test/is (is-sorted (sort-parts-explicit-lazy [[]])))
(clojure.test/is (is-sorted (sort-parts-explicit-lazy [[1]])))
(clojure.test/is (is-sorted (sort-parts-explicit-lazy [[1 2]])))
(clojure.test/is (is-sorted (sort-parts-explicit-lazy [[1 2 3]])))
(clojure.test/is (is-sorted (sort-parts-explicit-lazy [[3 2 1]])))
(clojure.test/is (is-sorted (sort-parts-explicit-lazy [[3 3 3]])))
So, it works.
Both functions are essentially the same.
However I have a bad feeling about this. The simultaneous use of recur
and full recursion, with the full recursion path "leaving" the loop in its middle in the same function feels weird, like unstructured code dropping a good, fat GOTO.
Here is the expression tree for reference:
We are far from from beta-reduction here. The "reduction" can only be understood using explicit instruction flow semantics. Okay, reality meets platonic ideals.
Is the loop-recur/full-recursion supposed to look like this? Is it a style problem that can be improved?