An idiomatic way to store state lookup tables with indexes in Clojure

I am new to Clojure and functional programming in general, and I struggled with the following problem. I would like to assign a unique and stable index to a series of tokens (strings). Since there will be much more searches than inserts, the hash map seemed to be the way to go.

In Java, I would write something in the lines

int last = 0; HashMap<String, Integer> lut = new HashMap<String, Integer>(); function Integer getIndex(String token) { Integer index = lut.get(token); if(index == null) last++; lut.put(token, last); return last; else { return index; } } 

The transliterated version in Clojure will be similar to

 (def last-index (atom 0)) (def lookup-table (atom {})) (defn get-index [token] (if (nil? (get @lookup-table token)) (do (swap! last-index inc) (swap! lookup-table assoc token @last-index) @last-index) (get @lookup-table token))) 

But this does not seem very idomatic, since it is mainly side effects and does not even hide it.

So, how would you do this without having two atoms to maintain state?

+7
source share
2 answers

Ankur's answer is not thread safe, although I don't think the description of this is very useful, and its alternatives are worse. It’s reasonable to say, “Well, I’m not worried about multiple threads now,” in which case this answer is fine. But it’s important to be able to write such things safely, even if you don’t need this guarantee in any particular instance, and the only safe way is to make all your logic inside swap! , eg:

 (let [m (atom {})] (defn get-index [token] (get (swap! m #(assoc % token (or (% token) (count %)))) token))) 

You can speed it up a bit by avoiding swap! if there is already a record when calling the function, and avoiding communication if there is already a record after you entered swap! , but you should "double check" that there is no record on the card for the current token before just assigning it (count %) , because some other thread could get in before the start of swap! ing (but after you decided to swap! ) and the value for the current token is assigned, in which case you should respect this assignment instead of creating a new one.

Edit: as an aside, the Java version, of course, has the same problem in terms of thread safety, because by default everything in Java is mutable and is not thread safe. At least in Clojure you have to put there ! : "Yes, I know it’s dangerous, I know what I’m doing."

So, in a way, Ankur's solution is a perfect translation of Java code, but it would be even better to improve it!

+3
source

One card in an atom will be enough:

 (def m (atom {})) ;adding new string to map (swap! m #(assoc %1 "Hello" (count %))) ;get an index (@m "Hello") (defn get-index [token] (or (@m token) ((swap! m #(assoc %1 token (count %))) token))) 

You basically tried to map imperative Java code to clojure and that is why you got this solution in your question. Try to think in terms of writing expressions, rather than thinking the step of a wise imperative style.

0
source

All Articles