Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way of "updating" a given key in a Clojure map?

Tags:

merge

map

clojure

Say we have a map that looks something like:

(def m {:a {:foo "bar"}})

Now we'd like to update the key :a in m with some new values:

(def vs {:baz "qux"})

If this were Python we could do something like:

>>> d = {'a': {'foo': 'bar'}}
>>> d['a'].update({'baz': 'qux'})
>>> d
{'a': {'foo': 'bar', 'baz': 'qux'}}

The simplest Clojure equivalent I found was to define a function like this:

(defn update-key
  "
  Updates a given key `k` over a map `m` with a map of values `vs`.
  "
  [k m vs]
  (assoc m k (merge (k m) vs)))

Which is then invoked like:

(update-key :a m vs)
; => {:a {:foo "bar" :baz "qux"}}

So my question is: What is the most idiomatic and correct way to achieve the same functionality as the update() method Python dicts provide?

like image 999
maxcountryman Avatar asked Mar 19 '13 22:03

maxcountryman


1 Answers

I think you're looking for assoc-in:

(def m {:a {:foo "bar"}})

(assoc-in m [:a :foo] "qux")
; => {:a {:foo "qux"}}

(assoc-in m [:a :baz] "qux")
; => {:a {:foo "bar", :baz "qux"}}

update-in is similar, and might be worth looking at too. This might actually be closer to your Python example:

(def m {:a {:foo "bar"}})
(def vs {:baz "qux"})

(update-in m [:a] merge vs)
; => {:a {:foo "bar", :baz "qux"}}

Update:

Even if the key is a variable value (not a compile-time constant) you can still use both update-in and assoc-in by putting the variable in the vector:

(def m {:a {:foo "bar"}})
(def k' :baz)
(def v' "qux")

(assoc-in m [:a k'] v')
; => {:a {:foo "bar", :baz "qux"}}

You can also build the keys vector programatically:

(def m {:a {:foo "bar"}})
(def k' :baz)
(def v' "qux")

(let [ks (conj [:a] k')]
  (assoc-in m ks v'))
; => {:a {:foo "bar", :baz "qux"}}
like image 139
DaoWen Avatar answered Nov 10 '22 02:11

DaoWen