Skip to content

Instantly share code, notes, and snippets.

@paultopia
Last active September 4, 2023 22:16
Show Gist options
  • Save paultopia/78ba6791d7eba48293da45d9d5d58c57 to your computer and use it in GitHub Desktop.
Save paultopia/78ba6791d7eba48293da45d9d5d58c57 to your computer and use it in GitHub Desktop.
merge for clojure that doesn't overwrite existing values with nil values
(require '[clojure.core.match :refer [match]])
(defn discard-nils [a b]
(match [a b]
[_ nil] a
:else b))
(def safe-merge (partial merge-with discard-nils))
;; examples
(safe-merge {:a 1 :b 1} {:a 5 :b nil} {:a nil :b 3})
;; => {:a 5, :b 3}
(merge {:a 1 :b 1} {:a 5 :b nil} {:a nil :b 3})
;; => {:a nil, :b 3}
(safe-merge {:a 1 :b nil} {:a nil :b 4 :c 5 :d nil})
;; => {:a 1, :b 4, :c 5, :d nil}
(merge {:a 1 :b nil} {:a nil :b 4 :c 5 :d nil})
;; => {:a nil, :b 4, :c 5, :d nil}
@paultopia
Copy link
Author

Use case for this: data collection and parsing projects that rely on maps stored in mutable state.

I'm working on a complex human/computer web data collection project that takes the html from a webpage via a chrome extension, parses it out, with a combination of enlive and regex, into a map, and sends it to a server to be stored in an atom until the human driving the browser answers some questions about it, at which time it's entered into a database.

But the webpage sometimes gets updated by ajax calls, and my chrome extension will scrape that data too and send it to the server to be merged with the existing data.

What happens if a subsequent update of the webpage destroys some data that my parsers found on the initial webpage? If I just merged the maps via the standard merge function in clojure.core, parsing the second version of the page would return nil for the destroyed data, and then merge would lose the first version of the page. That's no good.

safe-merge prefers later data to former, but prefers non-nil data to nil data, and the preference for non-nil trumps the preference for later data. Problem solved.

@igrishaev
Copy link

Using core.match looks a bit overengineered here. if/else logic would be enough:

(defn discard-nils
  [a b]
  (if (nil? b) a b))

@D00mch
Copy link

D00mch commented Sep 4, 2023

(def safe-merge (partial merge-with #(or %2 %1)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment