PurelyFunctional.tv Newsletter 313: Always use the 3-argument version of reduce
From OO to Clojure Workshop!
Watch my free workshop to help you learn Clojure faster and shift your paradigm to functional.
Issue 313 - February 11, 2019 · Archives · Subscribe
Clojure Tip 💡
Clojure Tip: Always use the 3-argument version of
2-argument version can have unexpected behavior.
Do you use
reduce? You should. It's one of the three functional
tools (along with
filter) that I made one of my first courses about. The
production quality is not as high as my newer courses, but the content
is good. Check it
out. It's free.
The three-argument version of
reduce is just great. However, the
two-argument version has unexpected behavior. Here's an example. Feel
free to try these at a REPL.
(reduce str ) ;=> "" (reduce str [1 2]) ;=> "12"
Given those expressions and their results, what would you expect from this?
(reduce str ) ;=> ????
From the examples above, I would expect
"1". But guess what I get?
the Long, not a String.
explicitly says If coll has only 1 item, it is returned and f is not
How could that ever work?
Well, in defense of this choice, it does work for lots of cases. For instance:
(reduce + ) ;=> 1 (reduce * ) ;=> 10
It works just fine when you don't need to call
f to get the right
answer. But if
f changes the type, or does any kind of calculation,
you will get the wrong answer. It's this kind of tricky, corner-casey
behavior can mess you up. It works just great until it doesn't.
By why does Clojure do that?
This behavior of
reduce was taken from Common Lisp. Rich Hickey has
expressed his regrets about
including it in Clojure (transcript
So what should I do instead?
The best and easiest change is to always use the three-argument version
of reduce. The third argument is the initial value, which is often the
identity of the function. In the case of the function
str, it's the
empty string. In the case of
0. In the case of
1. When a function has a value like this, it's called the identity
of that function. Not all functions have an identity.
And you won't always start with the identity. Sometimes you start from
reduce lets you do that because it's just an argument.
You can start from wherever you want.
reduce is very practical and powerful. It's considered universal
recursion over lists. That means that you can define any other
recursive list operation using
reduce. For instance,
filter can be built using
reduce. In fact, we do just that in the
This course has a ton of stuff you may want to learn about
including how to use it, how implement it, and some complementary
Clojure News ⚡️
The State of Clojure 2019 results are in. Cognitect has a blog post analyzing the results, written by Alex Miller. Daniel Compton did his own analysis. He has done this for a few years in a row. He organizes the free responses and picks ones that stand out. I appreciate the qualitative understanding of people's perceptions of Clojure. The Apropos panel talked about this, as well.
I consider this survey an important source of information about where I can add value to Clojurists---especially this rank ordering of "frustrations".
And I appreciate that 96% of respondents are happily using Clojure.
Brain skills 😎
There's a lot of scientific evidence that memories are formed and consolidated during REM sleep. Be sure to get plenty of good sleep when you're learning new material, like Clojure. And sleep is important for your health in general, so get plenty! For more information, read Why We Sleep by Matthew Walker. It's a great introduction to all the benefits of sleep and all the dangers of sleep deprivation.
Clojure Teaser 🤔
Here's a nice puzzle for you. I'll reveal the answer in the next issue.
Here is some code to print out a counter.
(def counter (atom 0)) (defn print-counter  (let [counter @counter] (println "===========") (println "| COUNTER |") (println (format "| %07d |" @counter)) (println "==========")))
But when I run it at the REPL, I get an error message about Futures. Am I using Futures? Is there something going on behind the scenes?
user=> (print-counter) =========== | COUNTER | Execution error (ClassCastException) at user/print-counter (REPL:5). class java.lang.Long cannot be cast to class java.util.concurrent.Future (java.lang.Long and java.util.concurrent.Future are in module java.base of loader 'bootstrap')
Can you figure out what's going on? Send me your answers and I'll share the answer next week.
See you then. And rock on! Eric Normand