Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the equivalent of Java's computeIfAbsent or putIfAbsent in Clojure?

With a map in Java you can write map.computeIfAbsent(key, f) that checks whether the key exists in the map already, and if not calls f on the key to generate a value, and enters that in the map. You also have map.putIfAbsent(key, value) which only puts the value in the map if the key does not already exist in the map.

Ignoring the fact the Java methods also return the value, and instead wanting the new map returned, what would be the equivalent code in Clojure?

The best I've come up with so far is to roll my own with something like

(defn compute-if-absent [map key f]
  (if (key map)
    map
    (assoc map key (f key))))

Is there an alternative to rolling my own?

like image 510
Graeme Moss Avatar asked Jun 14 '16 08:06

Graeme Moss


2 Answers

Clojure maps are immutable so you will always return a new map with updated contents - thus your functions will be always thread safe. It also means that you can't have a global variable holding your map and mutate them in place.

Your implementation of compute-if-absent is almost correct. It will fail for keys that are falsey, for example {false 1}. You need to change your if condition and use contains? instead of key:

(defn compute-if-absent [m k f]
  (if (contains? m k)
    m
    (assoc m key (f k))))

If you do need to have the same behaviour as ConcurrentMap you can just use them using Java interop in Clojure.

like image 148
Piotrek Bzdyl Avatar answered Oct 24 '22 23:10

Piotrek Bzdyl


Even simpler solution would be to use merge, for put-if-absent:

(merge {key val} m)

Although the end result would be the same for compute-if-absent, the following code would of course compute (f key) regardless of the contents of m:

(merge {key (f key)} m)
like image 43
z7sg Ѫ Avatar answered Oct 24 '22 23:10

z7sg Ѫ