commute records an update to a ref inside a dosync transaction when concurrent update operations commute, allowing the operation’s ordering to be independent. It is useful for additive updates such as incrementing vote counts. section 14.4
Use commute when the update can be safely combined regardless of operation order:
- Apply it through an updating function such as update, as in (commute votes update candidate inc). section 14.4
- Keep the operation inside dosync, where refs coordinate as part of STM transactions. section 14.4
- Do not perform I/O or other transaction-forbidden side effects in a function used by commute. section 14.4
Concurrent vote counting
Two futures repeatedly execute (commute votes update pref (fnil inc 0)) inside dosync. The resulting ref contains accumulated counts such as {"candidate-0" 50, "candidate-1" 153}, despite concurrent counters contributing updates. section 14.4
(def votes (ref {})) ; #1
(defn counter [poll votes] ; #2
(future
(dosync
(doseq [pref poll]
(commute votes update pref (fnil inc 0))))))
(defn generate-poll [& preference] ; #3
(eduction
(map-indexed #(repeat %2 (str "candidate-" %1)))
cat
preference))
(let [c1 (counter (generate-poll 40 64 19 82 11) votes) ; #4
c2 (counter (generate-poll 10 89 23 75 22) votes)]
[@c1 @c2]
@votes)
;; {"candidate-0" 50 ; #5
;; "candidate-1" 153
;; "candidate-2" 42
;; "candidate-3" 157
;; "candidate-4" 33}
Browse the complete concurrent vote-counting example, including poll generation, futures, commute-based updates, and resulting counts. section 14.4
