(reduce)
One habit I had to initially overcome coming to Clojure was a tendency to loop recur all the time instead of reducing. Here’s a snippet from #clojure where I came to ask about the habit that I saved from that time:
neeasade hello all – newbie clojure user here, and I’ve noticed I tend to solve almost all my problems with loop+recur forms. just wondering if that’s like, maybe a sign I’m still doing stuff procedurally.
justin_smith neeasade: yeah - for example any time you do (recur (rest x)) is a sign you are doing it the hard way. clojure has many functions to abstract variants of that pattern (traversing a collection in order) without needing to explicitly do a procedural loop. then it’s a question of learning what the various collection functions are useful for (filter, map, mapcat, reduce, for etc.).
neeasade I guess the problem I have is I feel like usually I have a collection of ’moves’ or ’ops’ that I want to apply to ’state’ and have state change along the way.
justin_smith that sounds like reduce or reductions to me.
neeasade maybe reduce is what I need to get comfy with then (I’m comf with map/reduce/filter).
justin_smith it caries a state (usually called an “accumulator” but it can be anything) between each step.
neeasade cool – thanks, I’ve use reducing/reducers before, I think I just fell out of habit because it doesn’t feel “natural” to me yet.
justin_smith:
(reduce (fn [acc i] (let [res (+ acc i)] (if (> res 100) (reduced res) res))) 0 (range)) ;; => 105
justin_smith neeasade: also reduce has a built in debugger, you can s/reduce/reductions/.
(reductions (fn [acc i] (let [res (+ acc i)] (if (> res 100) (reduced res) res))) 0 (range)) ;; => (0 0 1 3 6 ...)
neeasade thanks justin_smith – appreciate it!
Now, let’s compare what this difference looks like with easy-ranked 4clojure problem:
Write a function which separates the items of a sequence by an arbitrary value.
;; replace the '__' (= (__ 0 [1 2 3]) [1 0 2 0 3]) (= (apply str (__ ", " ["one" "two" "three"])) "one, two, three") (= (__ :z [:a :b :c :d]) [:a :z :b :z :c :z :d])
Before (bad habits):
(fn [sep coll & state] (if (= 1 (count coll)) (conj state (first coll)) (recur sep (rest coll) (conj (or state []) (first coll) sep))))
We’re doing a lot of manual management of state here, to carry it through the solution.
After:
(fn [sep coll] (butlast (reduce (fn [state next] (conj state next sep)) [] coll)))
Just gotta remember to embrace reducin’, and the implicit carrying of state it gives us.