Spindel provides a runtime-backed atom that is API-compatible with
clojure.core/atom but stores its state inside the execution context. The
benefit: atoms participate in fork isolation, snapshot/restore, and
serialization just like signals do.
(require '[org.replikativ.spindel.core :as s])
(s/with-context ctx
(let [cache (s/atom [])]
(swap! cache conj :hello)
@cache)) ;; => [:hello]
clojure.core/atomUse s/atom whenever the atom needs to:
snapshot-context / restore-snapshot round-tripUse clojure.core/atom for ephemeral runtime helpers that have nothing to
do with the reactive context — for instance, a one-shot accumulator inside
a private function. Plain Clojure atoms are NOT fork-safe: writes happen
on a single shared cell regardless of which context is bound.
(s/atom initial-value)
(s/atom initial-value :meta {…})
s/atom reads the dynamically bound *execution-context* at call time. To
construct an atom for a specific context, use s/create-atom:
(s/create-atom initial-value :meta {…})
The returned object implements IDeref, IAtom, and IRef exactly like
clojure.core/atom:
@a ;; deref current value
(swap! a f & args) ;; atomic update
(reset! a v) ;; replace
(add-watch a key callback) ;; classic watch
(remove-watch a key)
Each fork sees its own copy of any atom that has been mutated in the fork:
(s/with-context root
(def cache (s/atom #{})))
(swap! cache conj :a) ;; root: #{:a}
(let [child (s/fork-context root)]
(s/with-context child
(swap! cache conj :b) ;; fork: #{:a :b}
@cache)) ;; => #{:a :b}
(s/with-context root
@cache) ;; root: #{:a} — fork's :b is invisible
Reads fall through to the parent until the fork mutates the atom; the mutation triggers the overlay backend's copy-on-write at the entity level.
Each atom registers a finalizer (Cleaner on the JVM,
FinalizationRegistry in browsers that support it) so that when nothing
references the atom value anymore, its [:atoms id] entry is dropped from
the runtime state map. You don't have to track lifetimes manually.
In CLJS environments without FinalizationRegistry (very old browsers),
the entry persists for the lifetime of the context. Drop the context to
reclaim.
add-watch registers the watcher inside the context state, so it forks
along with the atom. A watcher added in a fork only fires for mutations
within that fork; the parent does not see it.
Atoms work the same inside spins:
(s/with-context ctx
(let [counter (s/atom 0)
bumper (s/spin
(let [x (s/await some-spin)]
(swap! counter inc)
x))]
@bumper
@counter))
Atoms are not reactive: signals are the right primitive when a value should drive re-execution of dependent spins. Atoms are for non-reactive state that nevertheless needs fork isolation.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |