Share your thoughts in the 2021 Clojure Community Survey!

Welcome! Please see the About page for a little more info on how this works.

0 votes
in Clojure by

It is sometimes important to merge based on the semantics of the data. Unfortunately, the current merge-with implementation makes you guess based on the datatype (value) what needs to be done. Semantics however requires the knowledge of what the key is that we are trying to merge

e.g. we have an application where a key :messages is an array and needs to be concatenated while merging, whereas we have a key :tags that needs to be replaced with the newest value

(merge-with into 
   {:messages [1 2 3] :tags [1 1 1]} 
   {:messages [4] :tags [4 4 4]}) 

=> {:messages [1 2 3 4] :tags [1 1 1 4 4 4]} ;; got
=> {:messages [1 2 3 4] :tags [4 4 4]} ;; expected

there is no way to do this with the current merge-with since it never supplies the key to the merging function

3 Answers

+1 vote
by
selected by
 
Best answer

merge-with is a splendid little tool, but it does not solve all problems. Not even "providing the key" will be enough for some cases. Fortunately, merge-with is not a primitive; feel free to make your own function that does what you need! Some off-the-shelf options with lots of bells and whistles: "Meander" (https://github.com/noprompt/meander) and "Specter" (https://github.com/redplanetlabs/specter)

by
I did stumble upon meander while looking for a solution :)

Not even "providing the key" will be enough for some cases -- curious what cases I missed
by
I concur with this - merge-with is what it is, if you want more there are plenty of options in existing utility libs, whether in the small (like http://plumatic.github.io/plumbing/plumbing.map.html#var-merge-with-key) or in the large with things like meander or specter.
+2 votes
by

This is a late answer, but a clean solution without bringing in anything except clojure.core would be to use reduce-kv instead of merge-with:

(defn newer [x y] (if (pos? (compare x y)) x y))
(defn combine [m k v]
  (case k
    :messages (update m k into v)
    :tags (update m k newer v)))
(reduce-kv combine
  {:messages [1 2 3] :tags [1 1 1]}
  {:messages [4] :tags [4 4 4]})
;; => {:messages [1 2 3 4] :tags [4 4 4]}
0 votes
by

also, it was never right to assume that there can be a single merging function that works for all the keys. e.g. if there is a key which is a scalar number, then into as a merging function fails with:

IllegalArgumentException Don't know how to create ISeq from: java.lang.Long

...