Transactional state
TxRef is the atom of STM — a
transactional reference, plus the Effect.tx / Effect.txRetry mechanics
that make groups of updates atomic.
Concurrent code that shares mutable state is hard to get right. The moment two
fibers read-modify-write the same data, you risk lost updates, partial writes,
and inconsistent reads. Effect’s answer is Software Transactional Memory
(STM): you mark a block of state changes as a transaction with Effect.tx,
and Effect guarantees that the whole block commits atomically — all of it,
or none of it — without you ever writing a lock by hand.
import { Effect, TxRef } from "effect"
// Move money between two accounts. The read, the check, and both writes// must all happen as a single atomic step — no other fiber can observe a// state where the money has left one account but not arrived in the other.const transfer = ( from: TxRef.TxRef<number>, to: TxRef.TxRef<number>, amount: number) => // Effect.tx is the transaction boundary: everything inside commits together Effect.tx( Effect.gen(function*() { const balance = yield* TxRef.get(from) if (balance < amount) { // Failing aborts the transaction — none of the writes below are kept return yield* Effect.fail("insufficient funds" as const) } yield* TxRef.update(from, (n) => n - amount) yield* TxRef.update(to, (n) => n + amount) }) )Effect’s STM is optimistic. A transaction body runs against a private journal of reads and writes rather than touching the shared values directly. At commit time Effect checks whether any value the transaction read was changed by another fiber in the meantime:
A transaction can also wait deliberately. Calling Effect.txRetry suspends the
transaction until one of the transactional values it read changes — turning
“there isn’t enough data yet” into a clean, declarative blocking primitive
instead of a polling loop.
Transactional state
TxRef is the atom of STM — a
transactional reference, plus the Effect.tx / Effect.txRetry mechanics
that make groups of updates atomic.
Transactional data structures
TxHashMap, TxChunk, TxQueue, TxHashSet, and
more — collections whose
every operation composes inside a transaction.
Coordination
TxSemaphore, TxReentrantLock, and
TxDeferred — permits, read/write locks, and
one-shot signals that commit atomically with your state.
Use the Tx* modules whenever more than one fiber touches a piece of state
and a single read-modify-write would race. The classic cases are transfers
between accounts, inventory and reservation systems, work queues, connection
pools, and any “wait until a condition holds” coordination.
For state owned by a single fiber, or where atomicity across multiple values is
not a concern, the simpler Ref and SynchronizedRef
primitives are a better fit — they have less overhead and no retry semantics.
STM earns its keep precisely when you need several values to move together.