Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use an atom to update a map's state?

Tags:

clojure

I want to create a function that holds the frequencies of strings I deem as spam. I'm trying to use an atomic map that can be updated as I give the map a new string.

Desired Behavior

user> (train-word spam "FREE")
{"FREE": 1}
user> (train-word spam "FREE")
{"FREE" : 2}
user> (train-word spam "MONEY")
{"FREE" : 2 "MONEY" : 1}

So far I have tried

(def spam (atom {}))
(defn train-word [m word]
  (swap! update-in m [word]
                 (fn [old] (inc (or old 0)))))

but that produces an error:

clojure.lang.Atom cannot be cast to clojure.lang.Associative  clojure.lang.RT.assoc (RT.java:702)

I'm new to Clojure so I did a quick prototype in Python

from collections import defaultdict
spam = defaultdict(int)
def train_word(word, spam):
        spam[word] += 1

What is the idiomatic way to manage state using an atom to update current values as well as add new ones? Thanks!

like image 926
idclark Avatar asked Dec 27 '13 23:12

idclark


1 Answers

Just need to pass the atom before the function in the call to swap!.

user> (def spam (atom {}))                                                        
#'user/spam 
user> (defn train-word [m word]  
        (swap! m update-in [word]  
               (fn [old] (inc (or old 0)))))                                      
#'user/train-word 
 user> (train-word spam "FREE")                                                    
{"FREE" 1}

swap! constructs the function call by:

  • Making a list out of everything after it's first argument,
    (update-in [word] (fn [old] (inc (or old 0)))
  • Getting the current value of the atom in it's first argument,
    @m
  • Insert that value as the second item in this list (just after the function name)
    (update-in @m [word] (fn [old] (inc (or old 0)))
  • Evaluate this list:
    {"FREE" 1}
  • put the result back in the atom.
like image 101
Arthur Ulfeldt Avatar answered Oct 24 '22 07:10

Arthur Ulfeldt