Dennis Hackethal’s Blog

My blog about coding, philosophy, and anything else that interests me.

Neat Clojure Function

Yesterday, I learned about the Clojure core function merge-with. Imagine you keep an inventory of two stores:

(def inventory-1 {:cornflakes 3 :chunky-soup 2 :nutella 2})
(def inventory-2 {:cornflakes 1 :chunky-soup 4 :milk 2})

Say you want to find out the total number of each item. In this case, a map saying that you have 4 boxes of cornflakes left, 6 chunky soups, 2 jars of Nutella, and 2 cartons of milk. How could you go about this? You can’t just merge them or the second map’s items will just override those it shares with the first one’s:

(merge inventory-1 inventory-2)
> {:cornflakes 1 :chunky-soup 4 :nutella 2 :milk 2}

This says you have, say, only 4 chunky soups total, when in reality you have 6.

Try merge-with instead:

(merge-with + inventory-1 inventory-2)
> {:cornflakes 4 :chunky-soup 6 :nutella 2 :milk 2}

What’s going on here? It basically iterates over the tuples in each map and constructs a new map that will have both maps’ keys. Whenever the given maps share a key, it passes both corresponding values to the given function (in this case +); otherwise, it just adds the remaining key and value to the result without invoking the given function.

In this particular case, it sees that :cornflakes occurs in both maps, once with value 3, and once with value 1, so the result will have an entry of :cornflakes (+ 3 1) . It also sees that :nutella only occurs in the first map, so an entry of :nutella 2 is added to the result. And so on.

This works for any number of maps:

(merge-with + {:a 1} {:a 2} {:b 1})
> {:a 3 :b 1}

Of course, it works for any function, not just +. What function you pass it is up to you. You may get creative; say you have a bunch of vectors of numbers in each key, and you want to concat them:

(merge-with concat {:a [1 2 3]} {:a [4]} {:b [1 5]})
> {:a (1 2 3 4) :b [1 5]}

(Note that while the value of :a value turned into a list (the result of concat:b‘s stayed a vector because concat was not invoked with it since no other map had :b for a key.)

Or perhaps you can add a bunch of integers up that are hidden within vectors behind keys? You sure can:

(merge-with (comp #(apply + %) concat)
  {:a [4 2 1]} {:a [7] :b [3]} {:b [3 2]})
> {:a 14 :b 8}

As you can see, merge-with is powerful. Neato, huh fellas?

PS: Guess how often I miss OOP when writing Clojure.

If you enjoyed this article, follow me on Twitter for more content like it.


What people are saying

What are your thoughts?

Markdown supported. cmd + enter to submit. You are responsible for what you write.