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)
Software Transactional Memory (STM) combines operations into one atomic 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.
Show page setup
// Setup for the checked examples on this page.
open System.Threading
open Axial
open Axial.PlatformService
open Axial.State
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actuallet withdraw (amount: decimal) (account: TRef<decimal>) : STM<decimal> =
stm {
let! balance = TRef.get account
// Wait until the account can cover the withdrawal instead of driving the balance negative.
if balance < amount then return! STM.retry
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 }
(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
Axial.State.STMretry: STM<'T>Signals that the current branch should retry once observed state changes. An STM operation that triggers a retry. In Axial Flow's current implementation, this suspends the transaction (without blocking the calling thread) until another transaction commits a change, then re-runs it from the start.
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
Requires & types
Requires: open Axial.State (packages: Axial)val withdraw: decimal -> TRef<decimal> -> STM<decimal>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>)
if balance < amount then
return! (STM.retry: STM<unit>)
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 }
(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
Axial.State.STMretry: STM<'T>Signals that the current branch should retry once observed state changes. An STM operation that triggers a retry. In Axial Flow's current implementation, this suspends the transaction (without blocking the calling thread) until another transaction commits a change, then re-runs it from the start.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
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
Requires & types
Requires: open Axial.State (packages: Axial)val withdrawAnnotated: decimal -> TRef<decimal> -> STM<decimal>let transferFunds (fromAcc: TRef<decimal>) (toAcc: TRef<decimal>) (amount: decimal) =
stm {
let! currentFrom = TRef.get fromAcc
// Retry later if the source balance cannot cover the transfer.
if currentFrom < amount then return! STM.retry
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 }
(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
Axial.State.STMretry: STM<'T>Signals that the current branch should retry once observed state changes. An STM operation that triggers a retry. In Axial Flow's current implementation, this suspends the transaction (without blocking the calling thread) until another transaction commits a change, then re-runs it from the start.
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"
Requires & types
Requires: open Axial.State (packages: Axial)val transferFunds: TRef<decimal> -> TRef<decimal> -> decimal -> STM<unit>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>Requires & types
Requires: open Axial; open Axial.State (packages: Axial)val processTransfer: TRef<decimal> -> TRef<decimal> -> decimal -> Flow<'a,'b,string>Wait and fall back
STM.retry suspends the transaction without blocking the thread; the next commit wakes it and the transaction
re-runs from the start. STM.orElse runs a fallback branch when the first branch retries:
let withdrawWaiting (amount: decimal) (account: TRef<decimal>) : STM<decimal> =
stm {
let! balance = TRef.get account
if balance < amount then return! STM.retry
do! TRef.set (balance - amount) account
return! TRef.get account
}
withdrawWaiting: 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 }
(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
Axial.State.STMretry: STM<'T>Signals that the current branch should retry once observed state changes. An STM operation that triggers a retry. In Axial Flow's current implementation, this suspends the transaction (without blocking the calling thread) until another transaction commits a change, then re-runs it from the start.
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
Requires & types
Requires: open Axial.State (packages: Axial)val withdrawWaiting: decimal -> TRef<decimal> -> STM<decimal>// A forked withdrawer suspends on an empty account; the deposit commit wakes it and it completes.
let waitForFunds =
flow {
let! account = STM.atomically (TRef.make 0m)
let! withdrawer = STM.atomically (withdrawWaiting 10m account) |> Flow.fork
do! STM.atomically (TRef.update (fun balance -> balance + 10m) account)
return! Fiber.join withdrawer
}
(waitForFunds |> Flow.run (ClockEnvironment Clock.live) : Exit<decimal, unit>) |> shouldEqual (Exit.Success 0m)
waitForFunds: Flow<ClockEnvironment,unit,decimal>flow: FlowBuilderThe universal flow { } computation expression.
account: TRef<decimal>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)
Axial.State.TRefModulemake: 'T -> STM<TRef<'T>>Creates a new with the initial value within an STM transaction. The initial value for the transactional reference. An STM operation that, when executed, produces a new transactional reference. let tx = stm { let! counter = TRef.make 0 return counter }
withdrawer: Fiber<unit,decimal>withdrawWaiting: decimal -> TRef<decimal> -> STM<decimal>(|>): '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.Flowfork: Flow<'env,'error,'value> -> Flow<'env,'none,Fiber<'error,'value>>Starts a flow in a new fiber without waiting for it to complete. Forking turns a cold flow description into hot child work and returns a handle that can later be joined or interrupted. Prefer zipPar or race when the caller only needs a simple parallel composition. Wait for the handle with Fiber.join or Fiber.await, and stop it with Fiber.interrupt. The fiber runs in its own scope and releases its scoped resources (anything registered with a scope, such as scopeResource, scopeFinalizer, or a hub subscription) when it settles, whether or not anyone joins it. A value the fiber returns therefore outlives the scoped resources the fiber acquired: a resource returned through Fiber.join has already been released. Use forkResource to acquire a resource in the background and keep it, or zipPar and sequencePar to acquire several in parallel. The flow to fork. A flow that produces a handle.
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"
Axial.FiberModuleOperations on a running , the handle returned by Flow.fork. Every operation returns a flow; nothing waits or interrupts until that flow runs. Reading a fiber's outcome through join, await, or interrupt marks it observed, so a defect it died with is not also reported as unobserved.
join: Fiber<'error,'value> -> Flow<'env,'error,'value>Waits for a fiber and returns its value, failing the same way the fiber failed. Joining preserves the child's error channel: a Cause.Fail becomes the same typed error, and interruption and defects remain interruption and defects. Use await to inspect the outcome instead. The fiber to join. A flow that completes with the fiber's value. let loadProfile : Flow<unit, string, string> = Flow.ok "profile" let loadOrders : Flow<unit, string, int list> = Flow.ok [ 1; 2 ] let page = flow { let! fiber = Flow.fork loadProfile let! orders = loadOrders let! profile = Fiber.join fiber return profile, orders }
run: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
Axial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
decimalAn abbreviation for the CLI type . Basic Types
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
shouldEqual: 'a -> 'a -> unitSuccessThe workflow completed successfully.
Requires & types
Requires: open Axial; open Axial.PlatformService; open Axial.State (packages: Axial, Axial.PlatformService)val waitForFunds: Flow<ClockEnvironment,unit,decimal>// When the first branch retries, orElse runs the fallback instead of waiting.
let fallback : Flow<unit, Never, string> =
flow {
let! account = STM.atomically (TRef.make 0m)
let! outcome =
STM.atomically (
STM.orElse
(stm {
let! balance = TRef.get account
if balance < 10m then return! STM.retry
return "withdrew" })
(stm { return "insufficient funds" }))
return outcome
}
fallback |> Flow.run () |> shouldEqual (Exit.Success "insufficient funds")
fallback: Flow<unit,Never,string>Axial.Flow`3unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
Axial.NeverRepresents an error channel that cannot occur.
stringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
account: TRef<decimal>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)
Axial.State.TRefModulemake: 'T -> STM<TRef<'T>>Creates a new with the initial value within an STM transaction. The initial value for the transactional reference. An STM operation that, when executed, produces a new transactional reference. let tx = stm { let! counter = TRef.make 0 return counter }
outcome: stringorElse: STM<'T> -> STM<'T> -> STM<'T>Tries the left branch and falls back to the right branch when the left branch retries. The primary STM transaction to attempt. The fallback STM transaction to run if the first one retries. A combined STM transaction that implements choice.
stm: StmBuilderThe stm { } computation expression for building atomic transactions.
balance: decimalget: 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 }
(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
retry: STM<'T>Signals that the current branch should retry once observed state changes. An STM operation that triggers a retry. In Axial Flow's current implementation, this suspends the transaction (without blocking the calling thread) until another transaction commits a change, then re-runs it from the start.
(|>): '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.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
Requires & types
Requires: open Axial; open Axial.State (packages: Axial)val fallback: Flow<unit,Never,string>// An interrupted retry withdraws its wake-up signal and the flow ends with Cause.Interrupt.
let interruptedSource = new CancellationTokenSource()
interruptedSource.Cancel()
let waiting : Flow<unit, unit, decimal> = STM.atomically (stm {
let! account = TRef.make 0m
let! balance = TRef.get account
if balance < 10m then return! STM.retry
return balance })
match waiting.RunSynchronously((), interruptedSource.Token) with
| Exit.Failure Cause.Interrupt -> ()
| other -> failwithf "Expected Cause.Interrupt, got %A" other
interruptedSource: CancellationTokenSourceSystem.Threading.CancellationTokenSourceSignals to a that it should be canceled.
Cancel: unit -> unitCommunicates a request for cancellation. This has been disposed. An aggregate exception containing all the exceptions thrown by the registered callbacks on the associated .
waiting: Flow<unit,unit,decimal>Axial.Flow`3unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
decimalAn abbreviation for the CLI type . Basic Types
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)
stm: StmBuilderThe stm { } computation expression for building atomic transactions.
account: TRef<decimal>Axial.State.TRefModulemake: 'T -> STM<TRef<'T>>Creates a new with the initial value within an STM transaction. The initial value for the transactional reference. An STM operation that, when executed, produces a new transactional reference. let tx = stm { let! counter = TRef.make 0 return counter }
balance: decimalget: 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 }
(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
retry: STM<'T>Signals that the current branch should retry once observed state changes. An STM operation that triggers a retry. In Axial Flow's current implementation, this suspends the transaction (without blocking the calling thread) until another transaction commits a change, then re-runs it from the start.
RunSynchronously: unit * CancellationToken option -> Exit<decimal,unit>Starts the workflow and blocks until the final exit is available. To bound how long the workflow may run, apply Flow.timeout to it: that interrupts the work and waits for its cleanup, where abandoning a blocked wait would leave it running. The environment used by the workflow. The optional cancellation token. Defaults to . The final workflow exit. .NET only
Token: CancellationTokenGets the associated with this . The associated with this . The token source has been disposed.
Axial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
FailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
InterruptAn administrative signal to stop the workflow (e.g., cancellation).
other: Exit<decimal,unit>failwithf: Printf.StringFormat<'T,'Result> -> 'TPrint to a string buffer and raise an exception with the given result. Helper printers must return strings. The formatter. The formatted result. See Printf.failwithf (link: ) for examples.
Requires & types
Requires: open Axial; open Axial.State; open System.Threading (packages: Axial)val interruptedSource: CancellationTokenSourceval waiting: Flow<unit,unit,decimal>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)
Requires & types
Requires: open Axial.State (packages: Axial)val deposit: TRef<decimal> -> decimal -> STM<unit>val batchTransfer: TRef<decimal> -> TRef<decimal> -> TRef<decimal> -> decimal -> Flow<'a,'b,unit>Manual lock calls can deadlock when code acquires multiple locks in different 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.
The global lock keeps the initial implementation simple while supporting transactional references, composable transactions, retry, and orElse.
API Reference
The TRef signatures (make, get, set, update) are in the
TRef API reference; the STM signatures (retry, orElse, atomically) are in
the STM API reference.
The STM torture test runs hundreds of interrupted transfers against an auditor and checks that every audit sees a consistent total.

