November 17, 2015

The Clojure “Reductions” Function

2 minute read


Tonight, while attempting a problem at 4 Clojure (problem number omitted so as not to give too many hints), I was struck by the following problem:

What if I need a Clojure function which can take a sequence of functions and can sequentially call them on some seed value?

I’m specifically looking at something like:

(defn some-fun
  [seed-val & funs]
  ; Keep passing the calculated value through, while also appending to sequence
  (list ((first funs) seed-val) ((second funs) ((first funs) seed-val)) ...))

I ended up hacking something together using iterate, but which wasn’t terribly elegant.

(defn some-fun
  [seed-val & funs]
  ; Cycle through the given functions by index and build
  ; the incremental result of calling them into a sequence using map
  (let [funs-len (count funs) funs (vec funs)]
    (map first (iterate (fn [[x i]] [((funs (mod i funs-len)) x) (inc i)]) [v 0]))))

When I compared my answer against a sample answer by a Clojure expert, I was blown away to see that Clojure already has a function which obviates all of my messy hand-rolled cycling: reductions.

Although the examples on Clojure Docs all reference the naive case of building a sequence of values from a single seed function and a (potentially) infinite sequence of values, the expert actually realized another application for this problem! Instead of seeding a function, the expert seeded a value to a (potentially) infinite sequence of functions! Specifically, a call was made to the cycle function to cause a list of functions to become an infinitely repeated sequence of functions, which could then be lazily “built up” over the seed value. For example, to retrieve the first 5 terms of the sequence

we can use the code

; Get the first 5 iterations of the sequence

(take 5 (reductions (fn [v f] (f v)) 1 (repeat (fn [x] (* 2 x)))))

Just another example of how crazy and clever functional programming can really be if you can train yourself to think functionally.

Edit:

Immediately after publishing this, I realized that the summary example could be replaced by the more idiomatic iterate function for the trivial case where there is only one function to call. That would look like

(take 5 (iterate (fn [v f] (f v)) 1 (fn [x] (* 2 x))))

© Jeff Rabinowitz, 2023