swap! receives an atom and a function as arguments. It applies the function to the atom’s current state to produce a new value, and then it updates the atom to refer to this new value. The new value is also returned. Here’s how you might increase Fred’s cuddle hunger level by one:
;; update in use get. (swap! fred update-in [:cuddle-hunger-level] + 10) ; => {:cuddle-hunger-level 22, :percent-deteriorated 1}
This is all interesting and fun, but what happens if two separate threads call (swap! fred increase-cuddle-hunger-level 1)? Is it possible for one of the increments to get lost the way it did in the Ruby example at Listing 10-1?
The answer is no! swap! implements compare-and-set semantics, meaning it does the following internally:
It reads the current state of the atom. It then applies the update function to that state. Next, it checks whether the value it read in step 1 is identical to the atom’s current value. If it is, then swap! updates the atom to refer to the result of step 2. If it isn’t, then swap! retries, going through the process again with step 1. This process ensures that no swaps will ever get lost.
Sometimes you’ll want to update an atom without checking its current value
(reset! fred {:cuddle-hunger-level 0 :percent-deteriorated 0})
watches and validators
Watches allow you to be super creepy and check in on your reference types’ every move. Validators allow you to be super controlling and restrict what states are allowable. Both watches and validators are plain ol’ functions.
watch
A watch is a function that takes four arguments: a key, the reference being watched, its previous state, and its new state. You can register any number of watches with a reference type.
(add-watch ref key watch-fn) ;; it's watch fn must be a fn of 4 args: a key ,the reference, its old-state,it's new-state.
(defn shuffle-speed [zombie] (* (:cuddle-hunger-level zombie) (- 100 (:percent-deteriorated zombie)))) (defn shuffle-alert [key watched old-state new-state] (let [sph (shuffle-speed new-state)] (if (> sph 5000) (do (println "Run, you fool!") (println "The zombie's SPH is now " sph) (println "This message brought to your courtesy of " key)) (do (println "All's well with " key) (println "Cuddle hunger: " (:cuddle-hunger-level new-state)) (println "Percent deteriorated: " (:percent-deteriorated new-state)) (println "SPH: " sph))))) (reset! fred {:cuddle-hunger-level 22 :percent-deteriorated 2}) (add-watch fred :fred-shuffle-alert shuffle-alert) (swap! fred update-in [:percent-deteriorated] + 1) ; => All's well with :fred-shuffle-alert ; => Cuddle hunger: 22 ; => Percent deteriorated: 3 ; => SPH: 2134
(swap! fred update-in [:cuddle-hunger-level] + 30) ; => Run, you fool! ; => The zombie's SPH is now 5044 ; => This message brought to your courtesy of :fred-shuffle-alert
(remove-watch fred :fred-shuffle-alert)
Validators
1 2 3 4 5 6 7 8 9 10 11 12
(defn percent-deteriorated-validator [{:keys [percent-deteriorated]}] (or (and (>= percent-deteriorated 0) (<= percent-deteriorated 100)) (throw (IllegalStateException. "That's not mathy!")))) (def bobby (atom {:cuddle-hunger-level 0 :percent-deteriorated 0} :validator percent-deteriorated-validator)) (swap! bobby update-in [:percent-deteriorated] + 200) ; This throws "Invalid reference state"
refs transaction
Refs allow you to update the state of multiple identities using transaction semantics. These transactions have three features:
They are atomic, meaning that all refs are updated or none of them are.
They are consistent, meaning that the refs always appear to have valid states. A sock will always belong to a dryer or a gnome, but never both or neither.
They are isolated, meaning that transactions behave as if they executed serially; if two threads are simultaneously running transactions that alter the same ref, one transaction will retry. This is similar to the compare-and-set semantics of atoms.
Clojure uses software transactional memory (STM) to implement this behavior.
(defn sock-count [sock-variety count] {:variety sock-variety :count count}) (defn generate-sock-gnome "Create an initial sock gnome state with no socks" [name] {:name name :socks #{}}) ;;(def sock-gnome (ref (generate-sock-gnome "Barumpharumph"))) (def sock-gnome (ref {:name "hello" :socks #{}})) (def dryer (ref {:name "LG 1337" :socks (set (map #(sock-count % 2) sock-varieties))}))
We’ll want to modify the sock-gnome ref to show that it has gained a sock and modify the dryer ref to show that it’s lost a sock. You modify refs using alter, and you must use alter within a transaction. dosync initiates a transaction and defines its extent; you put all transaction operations in its body. Here we use these tools to define a steal-sock function, and then call it on our two refs:
There are a couple of details to note here: when you alter a ref, the change isn’t immediately visible outside of the current transaction. This is what lets you call alter on the dryer twice within a transaction without worrying about whether dryer will be read in an inconsistent state. Similarly, if you alter a ref and then deref it within the same transaction, the deref will return the new state.
commute allows you to update a ref’s state within a transaction, just like alter. However, its behavior at commit time is completely different.
Here’s how alter behaves:
1 2 3 4
1. Reach outside the transaction and read the ref’s current state. 2. Compare the current state to the state the ref started with within the transaction. 3. If the two differ, make the transaction retry. 4. Otherwise, commit the altered ref state.
commute, on the other hand, behaves like this at commit time:
1 2 3
1. Reach outside the transaction and read the ref’s current state. 2. Run the commute function again using the current state. 3. Commit the result.
Notice two important details here. First, you use ^:dynamic to signal to Clojure that a var is dynamic. Second, the var’s name is enclosed by asterisks. Lispers call these earmuffs, which is adorable. Clojure requires you to enclose the names of dynamic vars in earmuffs.
The atom reference type allows you to create an identity that you can safely update to refer to new values using swap! and reset!. The ref reference type is handy when you want to update more than one identity using transaction semantics, and you update it with alter! and commute!.
thread acts almost exactly like future: it creates a new thread and executes a process on that thread. Unlike future, instead of returning an object that you can dereference, thread returns a channel. When thread’s process stops, the process’s return value is put on the channel that thread returns:
THe Hot dog machine process you’ve been longing for
(defn append-to-file "Write a string to the end of a file" [filename s] (spit filename s :append true))
(defn format-quote "Delineate the beginning and end of a quote because it's convenient" [quote] (str "=== BEGIN QUOTE ===\n" quote "=== END QUOTE ===\n\n"))
(defn random-quote "Retrieve a random quote and format it" [] (format-quote (slurp "http://www.braveclojure.com/random-quote")))
You can call methods on an object using (.methodName object). For example, because all Clojure strings are implemented as Java strings, you can call Java methods on them:
(spit "/tmp/hercules-todo-list" "- kill dat lion brov - chop up what nasty multi-headed snake thing")
(slurp "/tmp/hercules-todo-list")
; => "- kill dat lion brov - chop up what nasty multi-headed snake thing" (let [s (java.io.StringWriter.)] (spit s "- capture cerynian hind like for real") (.toString s)) ; => "- capture cerynian hind like for real"
(let [s (java.io.StringReader. "- get erymanthian pig what with the tusks")] (slurp s)) ; => "- get erymanthian pig what with the tusks"
The with-open macro is another convenience: it implicitly closes a resource at the end of its body, ensuring that you don’t accidentally tie up resources by forgetting to manually close the resource.