Reading — step 1 of 4
Learn
~1 min readSTM and Lazy Sequences
For coordinated multi-cell mutation, Clojure has STM — Software Transactional Memory. Refs are STM-backed cells.
(def account-a (ref 1000))
(def account-b (ref 500))
(defn transfer! [from to amount]
(dosync
(alter from - amount)
(alter to + amount)))
(transfer! account-a account-b 200)
@account-a ;; 800
@account-b ;; 700
dosync runs its body as a transaction:
- Both
altercalls happen, or neither - If another thread modified one of the refs in the middle, the transaction RETRIES
- Reading a ref outside
dosyncis fine (always sees a consistent value)
alter vs commute vs ref-set:
alter f— apply f to current value, set to result. Reads guaranteed consistent.commute f— same, but can commute with concurrent transactions if f is order-independent. Faster for counters.ref-set v— directly replace value, regardless of current.
ACID-ish guarantees (in-memory):
- Atomicity — all changes happen together
- Consistency — invariants between refs hold
- Isolation — transactions don't see partial state of others
- (Durability — no, it's in-memory)
STM vs atoms vs agents:
- atoms — single-cell, uncoordinated, fast (lock-free CAS)
- refs + STM — multi-cell, coordinated, automatic retry on conflict
- agents — async, single-cell, ordered updates
For 99% of Clojure code, atoms are enough. Use refs when you need to update multiple cells atomically and consistently — like the bank transfer example.
ensure — declare "I'm reading this ref and it must not change during my transaction":
(dosync
(let [balance (ensure account-a)]
;; balance won't change under us during this transaction
...))
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…