Created
March 11, 2015 21:21
-
-
Save danielpcox/c70a8aa2c36766200a95 to your computer and use it in GitHub Desktop.
Simple, recursive deep-merge in Clojure.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns deep-merge-spec | |
(:require [midje.sweet :refer :all] | |
[util :as u])) | |
(fact (u/deep-merge {:one 1 :two 2} | |
{:one 1 :two {}}) | |
=> {:one 1 :two {}}) | |
(fact (u/deep-merge {:one 1 :two {:three 3 :four {:five 5}}} | |
{:two {:three {:test true}}}) | |
=> {:one 1 :two {:three {:test true} :four {:five 5}}}) | |
(fact (u/deep-merge {:one {:two {:three 3}} | |
:four {:five {:six 6}}} | |
{:one {:seven 7 :two {:three "three" :nine 9}} | |
:four {:eight 8 :five 5} | |
:ten 10}) | |
=> {:one {:two {:three "three" :nine 9} | |
:seven 7} | |
:four {:five 5 :eight 8} | |
:ten 10}) | |
(fact (u/deep-merge {:one {:two 2 :three 3}} | |
{:one {:four 4 :five 5}}) | |
=> {:one {:two 2 :three 3 :four 4 :five 5}}) | |
(fact (u/deep-merge {:one 1 :two {:three 3}} | |
{:one 2 :two {:three 4}} | |
{:one 3 :two {:three 5}} | |
{:one 4 :two {:three 6}}) | |
=> {:one 4 :two {:three 6}}) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns util) | |
(defn deep-merge [v & vs] | |
(letfn [(rec-merge [v1 v2] | |
(if (and (map? v1) (map? v2)) | |
(merge-with deep-merge v1 v2) | |
v2))] | |
(when (some identity vs) | |
(reduce #(rec-merge %1 %2) v vs)))) |
but
(merge {:a 1} {:a nil})
{:a nil}
In deep merge I would prefer this semantic.
But that is of course a matter of taste.
And as @dijonkitchen mentioned
After using deep-merge for a bit, I think I know why it doesn't already exist. It encourages difficult to understand nested maps when flat maps are simpler/better. Perhaps its absence speaks to it as a code smell.
Of course you can modify the code to fit your needs.
Just for some shameless self promotion:
https://github.com/JasonStiefel/clojure-deep-merge/blob/master/src/deep/merge.clj
Here's one more version.
This one is a lot less concise, but potentially easier to understand. It might also perform better than some other versions, since I'm using loops and recurs.
(defn merge-entries [value-fn into-map entries]
(loop [result into-map
remaining (seq entries)]
(if-not remaining
result
(let [entry (first remaining)
k (key entry)
new-val (val entry)
cur-val (get result k)]
(recur (if (nil? cur-val)
(assoc result k new-val)
(assoc result k (value-fn cur-val new-val)))
(next remaining))))))
(defn merge-maps [value-fn & maps]
(loop [results nil
remaining (seq maps)]
(if-not remaining
results
(recur (merge-entries value-fn (or results {}) (-> remaining first seq))
(next remaining)))))
(defn deep-merge [& maps]
(let [value-fn (fn [v1 v2]
(if-not (and (map? v1) (map? v2))
v2
(deep-merge v1 v2)))]
(apply merge-maps value-fn maps)))
(deep-merge {:m {:a 1 :b 2}} {:m nil} {:m {:a 2 :c 3}})
(deep-merge {:m {:a 1 :b 2}} {:m {}} {:m {:a 2 :c 3}})
(deep-merge {:a {:b true}} {:a {:b false}} {:a {:b nil}})
(deep-merge {:a 1} nil)
(deep-merge {:a {:b true}} {:a {:b false}} {:a {:b nil}})
(deep-merge {:one 1 :two {:three 3}}
{:one 2 :two {:three 4}}
{:one 3 :two {:three 5}}
{:one 4 :two {:three 6}})
{:m {:a 2, :c 3}}
{:m {:a 2, :b 2, :c 3}}
{:a {:b nil}}
{:a 1}
{:a {:b nil}}
{:one 4, :two {:three 6}}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@fl00r well, my argument here is that, when using just
merge
:and I think of
nil
as kinda "absence of value"