How to use "Update-in" in Clojure?
I'm trying to use Clojure's up开发者_如何学Godate-in function but I can't seem to understand why I need to pass in a function?
update-in
takes a function, so you can update a value at a given position depending on the old value more concisely. For example instead of:
(assoc-in m [list of keys] (inc (get-in m [list of keys])))
you can write:
(update-in m [list of keys] inc)
Of course if the new value does not depend on the old value, assoc-in
is sufficient and you don't need to use update-in
.
This isn't a direct answer to your question, but one reason why a function like update-in
could exist would be for efficiency—not just convenience—if it were able to update the value in the map "in-place". That is, rather than
- seeking the key in the map,
- finding the corresponding key-value tuple,
- extracting the value,
- computing a new value based on the current value,
- seeking the key in the map,
- finding the corresponding key-value tuple,
- and overwriting the value in the tuple or replacing the tuple with a new one
one can instead imagine an algorithm that would omit the second search for the key:
- seek the key in the map,
- find the corresponding key-value tuple,
- extract the value,
- compute a new value based on the current value,
- and overwrite the value in the tuple
Unfortunately, the current implementation of update-in
does not do this "in-place" update. It uses get
for the extraction and assoc
for the replacement. Unless assoc
is using some caching of the last looked up key and the corresponding key-value tuple, the call to assoc
winds up having to seek the key again.
I think the short answer is that the function passed to update-in lets you update values in a single step, rather than 3 (lookup, calculate new value, set).
Coincidentally, just today I ran across this use of update-in in a Clojure presentation by Howard Lewis Ship:
(def in-str "this is this")
(reduce
(fn [m k] (update-in m [k] #(inc (or % 0))))
{}
(seq in-str))
==> {\space 2, \s 3, \i 3, \h 2, \t 2}
Each call to update-in takes a letter as a key, looks it up in the map, and if it's found there increments the letter count (else sets it to 1). The reduce drives the process by starting with an empty map {} and repeatedly applies the update-in with successive characters from the input string. The result is a map of letter frequencies. Slick.
Note 1: clojure.core/frequencies is similar but uses assoc! rather than update-in.
Note 2: You can replace #(inc (or % 0)) with (fnil inc 0). From here: fnil
A practical example you see here.
Type this snippet (in your REPL):
(def my-map {:useless-key "key"})
;;{:useless-key "key"}
(def my-map (update-in my-map [:yourkey] #(cons 1 %)))
;;{:yourkey (1), :useless-key "key"}
Note that :yourkey
is new. So the value - of :yourkey
- passed to the lambda is null
. cons
will put 1
as the single element of your list. Now do the following:
(def my-map (update-in my-map [:yourkey] #(cons 25 %)))
;;{:yourkey (25 1), :useless-key "key"}
And that is it, in the second part, the anonymous function takes the list - the value for :yourkey - as argument and just cons 25 to it.
Since our my-map
is immutable, update-in
will always return a new version of your map letting you do something with the old value of the given key.
Hope it helped!
精彩评论