开发者

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!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜