yazz.com
yazz.com

Reputation: 58786

How to use "Update-in" in Clojure?

I'm trying to use Clojure's update-in function but I can't seem to understand why I need to pass in a function?

Upvotes: 17

Views: 9928

Answers (4)

Markc
Markc

Reputation: 1057

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

Upvotes: 4

paulosuzart
paulosuzart

Reputation: 1092

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!

Upvotes: 2

seh
seh

Reputation: 15259

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.

Upvotes: 6

sepp2k
sepp2k

Reputation: 370162

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.

Upvotes: 31

Related Questions