Up: GROVE
Last edit: <2020-11-04>

(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.

(reduce)