Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptSTM (Software Transactional Memory)
open Axial
open Axial.State
AxialStateSoftware Transactional Memory (STM) is a concurrency primitive that lets you compose multiple atomic operations into a single transaction.
Ref updates one variable atomically. STM updates several variables in one atomic transaction, and supports retry / orElse style coordination for transactions
that need to wait on state changes or fall back to alternate branches.
Core Concepts
TRef<'T>: A transactional reference. Similar toRef<'T>, but designed to be used inside an STM transaction.stm { ... }: A computation expression used to compose transactional operations.STM.retry: Aborts the current branch and waits for a committed state change before retrying.STM.orElse: Tries a fallback branch when the first branch retries.STM.atomically: The bridge that executes anstmblock as a single atomic effect within aflow.
Basic Usage
Defining a Transaction
Use stm {} to combine reads and writes into one transaction.
let! binds the value produced by an STM step to the name on its left. do! binds an STM step returning unit.
return! uses another STM value as the result of the transaction.
let withdraw (amount: decimal) (account: TRef<decimal>) : STM<decimal> =
stm {
let! balance = TRef.get account
do! TRef.set (balance - amount) account
return! TRef.get account
}
withdraw: decimal -> TRef<decimal> -> STM<decimal>amount: decimaldecimalAn abbreviation for the CLI type . Basic Types
account: TRef<decimal>Axial.State.TRef`1Represents a transactional reference that can be updated atomically within an transaction. The type of the value stored in the reference.
Axial.State.STM`1Represents a transactional operation that can be composed, retried, and executed atomically. The type of the value produced by the operation.
stm: StmBuilderThe stm { } computation expression for building atomic transactions.
balance: decimalAxial.State.TRefModuleget: TRef<'T> -> STM<'T>Reads the current value of the transactional reference within a transaction. The transactional reference to read. An STM operation that produces the current value of the reference. let tx (counter: TRef<int>) = stm { let! value = TRef.get counter return value }
set: 'T -> TRef<'T> -> STM<unit>Sets the value of the transactional reference within a transaction. The new value to store in the reference. The transactional reference to update. An STM operation that sets the reference value. let tx (counter: TRef<int>) = stm { do! TRef.set 10 counter }
(-): ^T1 -> ^T2 -> ^T3Overloaded subtraction operator The first parameter. The second parameter. The result of the operation. 10 - 2 // Evaluates to 8
Here is the same transaction with the left- and right-hand types shown:
let withdrawAnnotated (amount: decimal) (account: TRef<decimal>) : STM<decimal> =
stm {
let! (balance: decimal) =
(TRef.get account: STM<decimal>)
do! (TRef.set (balance - amount) account: STM<unit>)
return! (TRef.get account: STM<decimal>)
}
withdrawAnnotated: decimal -> TRef<decimal> -> STM<decimal>amount: decimaldecimalAn abbreviation for the CLI type . Basic Types
account: TRef<decimal>Axial.State.TRef`1Represents a transactional reference that can be updated atomically within an transaction. The type of the value stored in the reference.
Axial.State.STM`1Represents a transactional operation that can be composed, retried, and executed atomically. The type of the value produced by the operation.
stm: StmBuilderThe stm { } computation expression for building atomic transactions.
balance: decimalAxial.State.TRefModuleget: TRef<'T> -> STM<'T>Reads the current value of the transactional reference within a transaction. The transactional reference to read. An STM operation that produces the current value of the reference. let tx (counter: TRef<int>) = stm { let! value = TRef.get counter return value }
set: 'T -> TRef<'T> -> STM<unit>Sets the value of the transactional reference within a transaction. The new value to store in the reference. The transactional reference to update. An STM operation that sets the reference value. let tx (counter: TRef<int>) = stm { do! TRef.set 10 counter }
(-): ^T1 -> ^T2 -> ^T3Overloaded subtraction operator The first parameter. The second parameter. The result of the operation. 10 - 2 // Evaluates to 8
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
let transferFunds (fromAcc: TRef<decimal>) (toAcc: TRef<decimal>) (amount: decimal) =
stm {
let! currentFrom = TRef.get fromAcc
do! TRef.set (currentFrom - amount) fromAcc
do! TRef.update (fun balance -> balance + amount) toAcc
}
transferFunds: TRef<decimal> -> TRef<decimal> -> decimal -> STM<unit>fromAcc: TRef<decimal>Axial.State.TRef`1Represents a transactional reference that can be updated atomically within an transaction. The type of the value stored in the reference.
decimalAn abbreviation for the CLI type . Basic Types
toAcc: TRef<decimal>amount: decimalstm: StmBuilderThe stm { } computation expression for building atomic transactions.
currentFrom: decimalAxial.State.TRefModuleget: TRef<'T> -> STM<'T>Reads the current value of the transactional reference within a transaction. The transactional reference to read. An STM operation that produces the current value of the reference. let tx (counter: TRef<int>) = stm { let! value = TRef.get counter return value }
set: 'T -> TRef<'T> -> STM<unit>Sets the value of the transactional reference within a transaction. The new value to store in the reference. The transactional reference to update. An STM operation that sets the reference value. let tx (counter: TRef<int>) = stm { do! TRef.set 10 counter }
(-): ^T1 -> ^T2 -> ^T3Overloaded subtraction operator The first parameter. The second parameter. The result of the operation. 10 - 2 // Evaluates to 8
update: ('T -> 'T) -> TRef<'T> -> STM<unit>Updates the value of the transactional reference within a transaction using the supplied function. The function to apply to the current value to produce the new value. The transactional reference to update. An STM operation that updates the reference value. let tx (counter: TRef<int>) = stm { do! TRef.update (fun n -> n + 1) counter }
balance: decimal(+): ^T1 -> ^T2 -> ^T3Overloaded addition operator The first parameter. The second parameter. The result of the operation. 2 + 2 // Evaluates to 4 "Hello " + "World" // Evaluates to "Hello World"
Running the Transaction
To execute an stm block, you use STM.atomically. This turns the transaction into a standard Flow.
let processTransfer fromAcc toAcc amount =
flow {
// Run the transaction atomically
do! STM.atomically (transferFunds fromAcc toAcc amount)
return "Transfer complete"
}
processTransfer: TRef<decimal> -> TRef<decimal> -> decimal -> Flow<'a,'b,string>fromAcc: TRef<decimal>toAcc: TRef<decimal>amount: decimalflow: FlowBuilderThe universal flow { } computation expression.
Axial.State.STMatomically: STM<'T> -> Flow<'env,'none,'T>Executes an STM transaction atomically within a flow while preserving retry/orElse coordination. The STM transaction to execute. A flow that performs the transaction and returns its result. Axial Flow's STM uses a single mutual-exclusion section around the commit/read step to ensure atomicity and coordinate retries. While this avoids complex optimistic concurrency control, it means that transactions are mutually exclusive and high contention can impact throughput. A transaction that retries does not block the calling thread (or, on Fable, the single JS thread) while it waits: it suspends on a broadcast signal that every successful commit resolves, then re-runs from the start. let transfer (fromAcc: TRef<int>) (toAcc: TRef<int>) amount = stm { let! bal = TRef.get fromAcc if bal < amount then do! STM.retry do! TRef.set (bal - amount) fromAcc do! TRef.update (fun b -> b + amount) toAcc } let flow = STM.atomically (transfer acc1 acc2 100)
transferFunds: TRef<decimal> -> TRef<decimal> -> decimal -> STM<unit>Composition
STM transactions are first-class values. You can compose multiple small transactions into a larger one using stm {} before ever calling atomically. Transactions built in separate functions combine into one atomic transaction.
let deposit (acc: TRef<decimal>) (amount: decimal) =
TRef.update (fun x -> x + amount) acc
let batchTransfer acc1 acc2 acc3 amount =
stm {
// Composite transaction
do! transferFunds acc1 acc2 amount
do! deposit acc3 (amount / 2.0m)
}
|> STM.atomically
deposit: TRef<decimal> -> decimal -> STM<unit>acc: TRef<decimal>Axial.State.TRef`1Represents a transactional reference that can be updated atomically within an transaction. The type of the value stored in the reference.
decimalAn abbreviation for the CLI type . Basic Types
amount: decimalAxial.State.TRefModuleupdate: ('T -> 'T) -> TRef<'T> -> STM<unit>Updates the value of the transactional reference within a transaction using the supplied function. The function to apply to the current value to produce the new value. The transactional reference to update. An STM operation that updates the reference value. let tx (counter: TRef<int>) = stm { do! TRef.update (fun n -> n + 1) counter }
x: decimal(+): ^T1 -> ^T2 -> ^T3Overloaded addition operator The first parameter. The second parameter. The result of the operation. 2 + 2 // Evaluates to 4 "Hello " + "World" // Evaluates to "Hello World"
batchTransfer: TRef<decimal> -> TRef<decimal> -> TRef<decimal> -> decimal -> Flow<'a,'b,unit>acc1: TRef<decimal>acc2: TRef<decimal>acc3: TRef<decimal>stm: StmBuilderThe stm { } computation expression for building atomic transactions.
transferFunds: TRef<decimal> -> TRef<decimal> -> decimal -> STM<unit>(/): ^T1 -> ^T2 -> ^T3Overloaded division operator The first parameter. The second parameter. The result of the operation. 16 / 2 // Evaluates to 8
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.State.STMatomically: STM<'T> -> Flow<'env,'none,'T>Executes an STM transaction atomically within a flow while preserving retry/orElse coordination. The STM transaction to execute. A flow that performs the transaction and returns its result. Axial Flow's STM uses a single mutual-exclusion section around the commit/read step to ensure atomicity and coordinate retries. While this avoids complex optimistic concurrency control, it means that transactions are mutually exclusive and high contention can impact throughput. A transaction that retries does not block the calling thread (or, on Fable, the single JS thread) while it waits: it suspends on a broadcast signal that every successful commit resolves, then re-runs from the start. let transfer (fromAcc: TRef<int>) (toAcc: TRef<int>) amount = stm { let! bal = TRef.get fromAcc if bal < amount then do! STM.retry do! TRef.set (bal - amount) fromAcc do! TRef.update (fun b -> b + amount) toAcc } let flow = STM.atomically (transfer acc1 acc2 100)
Using manual lock calls is error-prone and often leads to deadlocks when different parts of your
code acquire multiple locks in inconsistent orders.
Axial's STM keeps commit atomicity inside the engine and adds retry / orElse coordination
so a transaction can wait for a relevant state change or fall back to another branch without
exposing lock management in user code.
Implementation Details
Axial's current STM implementation runs each transaction under one global lock rather than an optimistic or lock-free model.
- Atomicity: The entire
stm {}block is executed while holding a global lock, ensuring no other transaction can interfere. - Suspending retry: When
STM.retryis called, the transaction suspends without blocking a thread until another transaction commits, then reruns from the start. Every commit wakes every waiting transaction. - Performance: Because of the global lock, transactions are mutually exclusive. This is suitable for coordinating low-frequency state changes but may become a bottleneck under high contention.
This design prioritizes correctness and simplicity for the initial release while keeping the usual STM programming
model: transactional references, composable transactions, retry, and orElse.
API Reference: Module TRef
| Function | Signature | Description |
|---|---|---|
make |
'T -> STM<TRef<'T>> |
Creates a new transactional reference. |
get |
TRef<'T> -> STM<'T> |
Reads the value of a TRef within a transaction. |
set |
'T -> TRef<'T> -> STM<unit> |
Sets the value of a TRef within a transaction. |
update |
('T -> 'T) -> TRef<'T> -> STM<unit> |
Atomically updates a TRef within a transaction. |
API Reference: Module STM
| Function | Signature | Description |
|---|---|---|
retry |
STM<'T> |
Requests that the current branch waits and reruns after a committed change. |
orElse |
STM<'T> -> STM<'T> -> STM<'T> |
Falls back to a second branch when the first branch retries. |
atomically |
STM<'T> -> Flow<'env, 'none, 'T> |
Executes a composed transaction as an atomic flow. |
The STM torture test runs hundreds of interrupted transfers against an auditor and checks that every audit sees a consistent total.

