Skuld.Effects.AtomicState (skuld v0.23.0)
View SourceAtomicState effect - thread-safe state for concurrent contexts.
Unlike the regular State effect which stores state in env.state (copied when
forking to new processes), AtomicState uses external storage (Agent) that can
be safely accessed from multiple processes.
Supports both simple single-state usage and multiple independent states via tags.
Handlers
AtomicState.Agent- Agent-backed handler for production (true atomic ops)AtomicState.Sync- State-backed handler for testing (no Agent processes)
Production Usage (Agent handler)
use Skuld.Syntax
alias Skuld.Effects.AtomicState
comp do
_ <- AtomicState.put(0)
_ <- AtomicState.modify(&(&1 + 1))
value <- AtomicState.get()
value
end
|> AtomicState.Agent.with_handler(0)
|> Comp.run!()
#=> 1Multiple States (explicit tags)
comp do
_ <- AtomicState.put(:counter, 0)
_ <- AtomicState.modify(:counter, &(&1 + 1))
count <- AtomicState.get(:counter)
_ <- AtomicState.put(:cache, %{})
_ <- AtomicState.modify(:cache, &Map.put(&1, :key, "value"))
cache <- AtomicState.get(:cache)
{count, cache}
end
|> AtomicState.Agent.with_handler(0, tag: :counter)
|> AtomicState.Agent.with_handler(%{}, tag: :cache)
|> Comp.run!()
#=> {1, %{key: "value"}}Compare-and-Swap (CAS)
comp do
_ <- AtomicState.put(10)
result1 <- AtomicState.cas(10, 20) # succeeds
result2 <- AtomicState.cas(10, 30) # fails - current is 20, not 10
{result1, result2}
end
|> AtomicState.Agent.with_handler(0)
|> Comp.run!()
#=> {:ok, {:conflict, 20}}Testing (Sync handler)
For testing without spinning up Agents, use the Sync handler:
comp do
_ <- AtomicState.put(0)
_ <- AtomicState.modify(&(&1 + 1))
AtomicState.get()
end
|> AtomicState.Sync.with_handler(0)
|> Comp.run!()
#=> 1Per-tag dispatch
Each tag gets its own handler sig (a module atom) and compact operation tuples. The tag is encoded in the sig, not in the operation args:
get→Comp.effect(sig(tag), AtomicState.Get)— bare atomput(v)→Comp.effect(sig(tag), {AtomicState.Put, v})— 2-tuplemodify(f)→Comp.effect(sig(tag), {AtomicState.Modify, f})atomic_state(f)→Comp.effect(sig(tag), {AtomicState.AtomicState, f})cas(e, n)→Comp.effect(sig(tag), {AtomicState.Cas, e, n})— 3-tuple
Summary
Functions
Install AtomicState handler via catch clause syntax.
Returns the env.state key used for storing the Agent pid for a given tag.
Returns the env.state key used for State-backed storage for a given tag.
Install an Agent-backed AtomicState handler.
Install a State-backed AtomicState handler for testing.
Functions
Install AtomicState handler via catch clause syntax.
Config selects handler type:
catch
AtomicState -> {:agent, 0} # agent handler
AtomicState -> {:agent, {0, tag: :counter}} # agent with opts
AtomicState -> {:sync, 0} # sync handler
AtomicState -> {:sync, {0, tag: :counter}} # sync with opts
Returns the env.state key used for storing the Agent pid for a given tag.
Returns the env.state key used for State-backed storage for a given tag.
@spec with_agent_handler(Skuld.Comp.Types.computation(), term(), keyword()) :: Skuld.Comp.Types.computation()
Install an Agent-backed AtomicState handler.
Delegates to AtomicState.Agent.with_handler/3.
@spec with_state_handler(Skuld.Comp.Types.computation(), term(), keyword()) :: Skuld.Comp.Types.computation()
Install a State-backed AtomicState handler for testing.
Delegates to AtomicState.Sync.with_handler/3.