A reduce Example Explained

From OO to Clojure Workshop!
Watch my free workshop to help you learn Clojure faster and shift your paradigm to functional.
Summary: A deep-dive into a single reduce
example shows how much can
happen in a short bit of code.
A few weeks ago I published a post with some annotated reduce examples. The idea was to explain using metaphor what reduce was doing and visually explain how those parts worked in the code.
I think it was pretty successful in general. But a reader asked me some great questions about one of the examples that got me thinking that I should explain it deeper.
Here is the example:
(reduce (fn [[n d] b]
[(+ n b)
(inc d)])
[0 0]
[1 2 3 4 5])
n
is the numerator, d
is the denominator
add each number to the numerator
add 1 to the denominator
start here
average these numbers, one at a time
The real problem with this example is the number of things going on at the same time. I don't want to distract people from the main point, which was that reduce itself is simple and easy to follow.
So now I'm going to focus on that example by itself, and annotate it even further.
First, notice that it has the normal reduce arguments: a function, an
initial value, and a collection. It might be confusing because the
initial value is itself a collection! This is a wonderful example of
something beautiful about reduce
: the first argument to the function,
the return value of the function, and the initial value should have
the same structure.
(reduce (fn [[n d] b]
[(+ n b) (inc d)])
[0 0]
[1 2 3 4 5])
a pair
a pair
a pair
In this example, they're all pairs. In Clojure, we represent pairs as
vectors
of two elements. So although the vector [1 2 3 4 5]
is also a vector,
it's semantically different. That one we treat as a sequence.
We can break this down a little further. We're using argument destructuring in the function:
(fn [[n d] b]
[(+ n b) (inc d)])
first [
indicates arguments; second [
indicates destructuring
Destructuring in Clojure is a convenient way to name parts of a collection without writing all of the parts the long way. The above function could be rewritten like this:
(fn [avg b]
(let [n (first avg)
d (second avg)]
[(+ n b) (inc d)]))
see, it's really two arguments
name the first element
name the second element
then return a pair of numbers
It's up to you how you want to write it. I like using destructuring. In a case like this, it's kind of a toss-up. As the destructuring gets longer, there's more of a savings. It's also a great way to indicate what your function expects as an argument. The longer way is not as clear. Two lines of naming distract from one line doing work.
Now, there's one more thing about this function. The return value is a
vector using the literal vector syntax, acting as a pair. The two
values of the pair will be evaluated, crammed into a vector, and
returned. That pair will hold two numbers. This is really just like
[0 0]
, except instead of literal 0
s, we have two expressions that
evaluate to numbers.
So let's go through the whole example once more:
(reduce (fn [[n d] b]
[(+ n b)
(inc d)])
[0 0]
[1 2 3 4 5])
2 arguments: pair of numbers (n
and d
) and a number (b
)
return a vector with n + b
and d + 1
initial pair of numbers passed as 1st argument
the numbers to be passed as b
, 2nd argument
Hold [0 0]
in your left hand. Walk down the list. When you get to 1
,
pick it up in your right hand. Now, add what's in your right hand to
the first number in your left hand, and add 1 to the second number in
your left hand. Hold the two new numbers in your left hand. Proceed down
the line, picking each number up in your right hand, until the end. The
answer is in your left hand.
At each step, you start with a pair, you do something to it, and you end with a pair. When you're done, you have a pair that is the answer.
Conclusions
Well, I hope that these explanations helped you see a little bit better how to read Clojure code. There are quite a few things to learn, and it takes a little practice, but reading or writing an expression like this does become second nature. That's why I so casually threw it out there, like it was self-explanatory. Sorry!
If you're into functional programming and I didn't scare you off from Clojure, you might be interested in LispCast Introduction to Clojure. It covers the basic syntax with a step-by-step sequence of exercises to make it second nature. It's fun: you get to teach a robot how to bake!