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.JavaScriptScheduling and Retries
A Schedule decides whether a flow should run again and how long to wait. Creating a schedule doesn't run anything. Apply it with Flow.retry or Flow.repeat.
Schedules don't store attempt state. Flow.retry and Flow.repeat track attempts for each run, so you can reuse one schedule value across unrelated flows.
Schedule works on .NET and Fable's JavaScript target.
How schedules work
A schedule makes two decisions after each flow execution:
- Whether to run the flow again.
- How long to wait before the next run.
The source flow always runs once before Axial consults the schedule. For example, Schedule.recurs 3 allows three more runs, for up to four runs in total.
runsUntilStopped counts attempts by a flow that always fails. firstOutputs reads a schedule's first outputs as a stream. The checks use millisecond delays because the waits happen during the test:
Show page setup
// Setup for the checked examples on this page.
open System
open Axial
open Axial.PlatformService
/// 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 runsUntilStopped (schedule: Schedule<ClockEnvironment, string, 'output>) : int =
let runs = ref 0
(Flow.delay (fun () ->
runs.Value <- runs.Value + 1
Flow.fail "always")
: Flow<ClockEnvironment, string, unit>)
|> Flow.retry schedule
|> Flow.run (ClockEnvironment Clock.live)
|> ignore
runs.Value
let firstOutputs (count: int) (schedule: Schedule<ClockEnvironment, unit, 'output>) : 'output list =
match schedule |> FlowStream.fromSchedule |> FlowStream.take count |> FlowStream.runCollect |> Flow.run (ClockEnvironment Clock.live) with
| Exit.Success outputs -> outputs
| Exit.Failure _ -> []
let ms (value: float) = TimeSpan.FromMilliseconds value
runsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intschedule: Schedule<ClockEnvironment,string,'output>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
outputintAn abbreviation for the CLI type . Basic Types
runs: int refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
Axial.Flowdelay: (unit -> Flow<'env,'error,'value>) -> Flow<'env,'error,'value>Defers flow construction until execution time. A function that returns the flow to execute. Recursive calls deferred here are consumed by the executor without growing a chain of parent tasks. Work that must run after the recursive call still needs storage until that call finishes. A flow that lazily evaluates the factory when executed. let flow = Flow.delay (fun () -> Flow.succeed 42)
Value: intThe current value of the reference cell
(+): ^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"
fail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
Axial.Flow`3unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
(|>): '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
retry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
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.
ignore: 'T -> unitIgnore the passed value. This is often used to throw away results of a computation. The value to ignore. ignore 55555 // Evaluates to ()
firstOutputs: int -> Schedule<ClockEnvironment,unit,'output> -> 'output listcount: intschedule: Schedule<ClockEnvironment,unit,'output>listThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
Axial.FlowStreamModulefromSchedule: Schedule<'env,unit,'output> -> FlowStream<'env,'error,'output>Creates a stream that emits a schedule's outputs, each after the delay the schedule chooses. The stream ends when the schedule stops. Delays are measured on the runtime's time, and a slow consumer does not shift a fixed-rate schedule: fromSchedule (Schedule.fixedRate period) emits on the start + n * period grid, skipping ticks the consumer was too busy to take instead of bursting. // A tick every 50 ms, aligned to when the stream started. Schedule.fixedRate (TimeSpan.FromMilliseconds 50.0) |> FlowStream.fromSchedule
take: int -> FlowStream<'a,'b,'c> -> FlowStream<'a,'b,'c>Emits at most values. stream |> FlowStream.take 10
runCollect: FlowStream<'a,'b,'c> -> Flow<'a,'b,'c list>Collects all emitted values into a list. stream |> FlowStream.runCollect
Axial.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.
outputs: 'output listFailureThe workflow failed due to a specific cause.
ms: float -> TimeSpanvalue: floatfloatAn abbreviation for the CLI type . Basic Types
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open Axial.PlatformService; open System (packages: Axial, Axial.PlatformService)val runsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intval firstOutputs: int -> Schedule<ClockEnvironment,unit,'output> -> 'output listval ms: float -> TimeSpanBuild a schedule
Choose a schedule based on how many times the flow can run and how long Axial should wait between runs.
Limit the number of recurrences
Use Schedule.recurs to set the number of additional runs.
// Run up to 6 times: 1 initial run and 5 additional runs.
let fiveMoreTimes = Schedule.recurs 5
fiveMoreTimes: Schedule<ClockEnvironment,string,int>Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
recurs: int -> Schedule<'env,'input,int>Creates a schedule that recurs a fixed number of times. counts the schedule's own decisions, not the total number of flow executions: Flow.retry and Flow.repeat always run the source flow once before consulting the schedule at all, so recurs 3 means 3 additional retries/repeats on top of that one free attempt: 4 executions in total, not 3. A Schedule value carries no state of its own (the attempt count lives in the retry/repeat call), so the same schedule value is safe to reuse across independent runs. The maximum number of additional times to recur, on top of the source flow's one free initial attempt. A schedule that recurs up to times, emitting the current attempt count (0 to n-1). let retryThreeTimes () = Schedule.recurs 3 // Flow.retry runs the source flow once for free, then consults the schedule at // attempts 0, 1, 2 (three retries) before giving up: 4 executions in total.
Requires & types
Requires: open Axial (packages: Axial)val fiveMoreTimes: Schedule<ClockEnvironment,string,int>runsUntilStopped fiveMoreTimes |> shouldEqual 6
runsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intfiveMoreTimes: Schedule<ClockEnvironment,string,int>(|>): '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
shouldEqual: 'a -> 'a -> unitUse a fixed delay
Use Schedule.spaced to keep running with the same delay between runs.
// Wait 1 second between runs.
let everySecond : Schedule<ClockEnvironment, string, _> = Schedule.spaced (TimeSpan.FromSeconds 1.0)
everySecond: Schedule<ClockEnvironment,string,int>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
System.TimeSpanRepresents a time interval.
FromSeconds: float -> TimeSpanReturns a that represents a specified number of seconds, where the specification is accurate to the nearest millisecond. A number of seconds, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open System (packages: Axial)val everySecond: Schedule<ClockEnvironment,string,int>The output counts the runs so far:
firstOutputs 3 (Schedule.spaced (ms 1.0)) |> shouldEqual [ 0; 1; 2 ]
firstOutputs: int -> Schedule<ClockEnvironment,unit,'output> -> 'output listAxial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
ms: float -> TimeSpan(|>): '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
shouldEqual: 'a -> 'a -> unitRequires & types
Requires: open Axial (packages: Axial)Schedule.spaced continues until the flow fails, is interrupted, or an outer operation stops it.
Use exponential backoff
Use Schedule.exponential when repeated attempts should wait progressively longer.
// Wait 100 ms, 200 ms, 400 ms, 800 ms, and so on.
let backoff : Schedule<ClockEnvironment, string, _> = Schedule.exponential (TimeSpan.FromMilliseconds 100.0)
backoff: Schedule<ClockEnvironment,string,TimeSpan>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open System (packages: Axial)val backoff: Schedule<ClockEnvironment,string,TimeSpan>Its output is the delay it chose:
firstOutputs 4 (Schedule.exponential (ms 1.0)) |> shouldEqual [ ms 1.0; ms 2.0; ms 4.0; ms 8.0 ]
firstOutputs: int -> Schedule<ClockEnvironment,unit,'output> -> 'output listAxial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
ms: float -> TimeSpan(|>): '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
shouldEqual: 'a -> 'a -> unitRequires & types
Requires: open Axial (packages: Axial)Add jitter
If many clients retry at the same time, they can place another burst of load on the service. Jitter spreads those retries across a wider period.
Schedule.jitteredWith multiplies each delay by a sampled factor from 0.5 to 1.5. You provide the sample function — a
unit -> float — which keeps randomness explicit and replaceable in tests.
That function is IRandom.NextDouble from Axial.PlatformService, so the schedule never constructs System.Random()
itself. Take the IRandom from your environment and pass its NextDouble method:
let policy (random: IRandom) : Schedule<ClockEnvironment, string, _> =
Schedule.exponential (TimeSpan.FromMilliseconds 100.0)
|> Schedule.jitteredWith random.NextDouble
policy: IRandom -> Schedule<ClockEnvironment,string,TimeSpan>random: IRandomAxial.PlatformService.IRandomProvides synchronous random-number generation.
Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
(|>): '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
jitteredWith: (unit -> float) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Adds jitter to a schedule's delay using a caller-supplied sample source. is not validated: a value outside [0.0, 1.0) is not rejected, it just produces a jitter factor outside the documented 0.5–1.5 range. The result is still always a valid, non-negative TimeSpan: negative factors clamp to and overflowing ones clamp to , the same as the base schedule's own overflow handling. This is deliberate: jitteredWith never throws for a badly-behaved sample source. A function returning a value in [0.0, 1.0), sampled once per attempt. Supply a deterministic function for reproducible schedules and tests. The base schedule to which jitter will be applied. A new schedule where each delay is multiplied by sample () + 0.5, giving a factor between 0.5 and 1.5, capped at . let schedule = Schedule.spaced (TimeSpan.FromSeconds 1.0) |> Schedule.jitteredWith (fun () -> 0.25) // Every delay becomes 750ms.
NextDouble: unit -> floatRequires & types
Requires: open Axial; open Axial.PlatformService; open System (packages: Axial, Axial.PlatformService)val policy: IRandom -> Schedule<ClockEnvironment,string,TimeSpan>In application code, the IRandom comes from the base runtime or
a field of your own environment record. In tests, pass a deterministic IRandom such as
Random.fromFixed 0 0.25 0uy, whose NextDouble always returns 0.25, so every delay is multiplied by 0.75:
let testPolicy : Schedule<ClockEnvironment, string, _> =
policy (Random.fromFixed 0 0.25 0uy)
testPolicy: Schedule<ClockEnvironment,string,TimeSpan>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
policy: IRandom -> Schedule<ClockEnvironment,string,TimeSpan>Axial.PlatformService.RandomHelpers for the random-number service.
fromFixed: int -> float -> byte -> IRandomCreates a deterministic random generator from fixed integer, double, and byte values.
Requires & types
Requires: open Axial; open Axial.PlatformService (packages: Axial, Axial.PlatformService)val testPolicy: Schedule<ClockEnvironment,string,TimeSpan>To build the jittered schedule from the environment rather than an ambient random source, read IRandom inside the
workflow and apply its NextDouble as the unit -> float sample:
let jitteredFetch (fetch: Flow<BaseRuntime, string, string>) : Flow<BaseRuntime, string, string> =
flow {
let! random = Random.service
let schedule =
Schedule.exponential (TimeSpan.FromMilliseconds 100.0)
|> Schedule.jitteredWith random.NextDouble
return! fetch |> Flow.retry schedule
}
jitteredFetch: Flow<BaseRuntime,string,string> -> Flow<BaseRuntime,string,string>fetch: Flow<BaseRuntime,string,string>Axial.Flow`3Axial.PlatformService.BaseRuntimeGroups the standard operational services commonly used by workflow hosts.
stringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
random: IRandomAxial.PlatformService.RandomHelpers for the random-number service.
service: Flow<'env,'error,IRandom>Reads the service from the environment.
schedule: Schedule<BaseRuntime,string,TimeSpan>Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
(|>): '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
jitteredWith: (unit -> float) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Adds jitter to a schedule's delay using a caller-supplied sample source. is not validated: a value outside [0.0, 1.0) is not rejected, it just produces a jitter factor outside the documented 0.5–1.5 range. The result is still always a valid, non-negative TimeSpan: negative factors clamp to and overflowing ones clamp to , the same as the base schedule's own overflow handling. This is deliberate: jitteredWith never throws for a badly-behaved sample source. A function returning a value in [0.0, 1.0), sampled once per attempt. Supply a deterministic function for reproducible schedules and tests. The base schedule to which jitter will be applied. A new schedule where each delay is multiplied by sample () + 0.5, giving a factor between 0.5 and 1.5, capped at . let schedule = Schedule.spaced (TimeSpan.FromSeconds 1.0) |> Schedule.jitteredWith (fun () -> 0.25) // Every delay becomes 750ms.
NextDouble: unit -> floatAxial.Flowretry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
Requires & types
Requires: open Axial; open Axial.PlatformService; open System (packages: Axial, Axial.PlatformService)val jitteredFetch: Flow<BaseRuntime,string,string> -> Flow<BaseRuntime,string,string>let attempts = ref 0
let transientFetch : Flow<BaseRuntime, string, string> =
flow {
attempts.Value <- attempts.Value + 1
if attempts.Value < 2 then return! Flow.fail "transient" else return "payload"
}
let runtime = { BaseRuntime.liveValue with Random = Random.fromFixed 0 0.25 0uy }
jitteredFetch transientFetch |> Flow.run runtime |> shouldEqual (Exit.Success "payload")
attempts: int refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
transientFetch: Flow<BaseRuntime,string,string>Axial.Flow`3Axial.PlatformService.BaseRuntimeGroups the standard operational services commonly used by workflow hosts.
stringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
Value: intThe current value of the reference cell
(+): ^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"
(<): '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.Flowfail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
runtime: BaseRuntimeAxial.PlatformService.BaseRuntimeModuleHelpers for constructing the standard explicit service bundle used by workflow hosts.
liveValue: BaseRuntimeCreates the standard live base runtime as an explicit service bundle.
Random: IRandomjitteredFetch: Flow<BaseRuntime,string,string> -> Flow<BaseRuntime,string,string>(|>): '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
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
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.PlatformService (packages: Axial, Axial.PlatformService)val attempts: int refval transientFetch: Flow<BaseRuntime,string,string>val runtime: BaseRuntimeRandom.service is the type-directed accessor for the IHasRandom contract; the fixed random makes the retried
attempt succeed deterministically.
Run at a fixed rate
Schedule.spaced waits a fixed delay after each run, so a run that takes 15 ms on a 50 ms spacing starts every 65 ms.
Schedule.fixedRate instead aligns runs to when the first run began: they start at start + n * period, however long
each takes.
// Start a control scan every 50 ms without drifting.
let scanOnce : Flow<ClockEnvironment, Never, unit> = Flow.ok ()
let scanLoop =
scanOnce
|> Flow.repeat (Schedule.fixedRate (TimeSpan.FromMilliseconds 50.0))
scanOnce: Flow<ClockEnvironment,Never,unit>Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.NeverRepresents an error channel that cannot occur.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
Axial.Flowok: 'value -> Flow<'env,'error,'value>Creates a successful synchronous flow. The value to wrap in a successful flow. A flow that always succeeds with the provided value.
scanLoop: Flow<ClockEnvironment,Never,unit>(|>): '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
repeat: Schedule<'env,'value,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Repeats a successful flow according to a schedule. The flow runs once, and after each success the schedule sees the value and decides whether to run it again and how long to wait. Any failure stops the repetition immediately. When the schedule stops, the flow succeeds with the last value. Each run has its own child scope, closed before the next run starts, so a repetition that lasts the life of an application does not accumulate finalizers. Decides, from each value, whether to repeat and after what delay. The flow to repeat. A flow that succeeds with the value of the last run. heartbeat |> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0))
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
fixedRate: TimeSpan -> Schedule<'env,'input,int>Recurs at a fixed rate aligned to when the first run began, emitting the recurrence count. Runs start at start + n * period, so the time a run takes does not shift later runs. A run that takes longer than a period is followed by one immediate run, after which the schedule realigns to the next boundary; missed ticks are skipped rather than run in a burst. Thrown when is not positive. scanOnce |> Flow.repeat (Schedule.fixedRate (TimeSpan.FromMilliseconds 50.0))
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open System (packages: Axial)val scanOnce: Flow<ClockEnvironment,Never,unit>val scanLoop: Flow<ClockEnvironment,Never,unit>If a run takes longer than a period, the next run starts immediately, once. The schedule then realigns to the next boundary, so missed ticks are skipped rather than run in a burst.
Combine and shape schedules
union, intersect, and andThen compose schedules; upTo sets a time budget; whileOutput/untilOutput,
elapsed, and map stop or transform on the schedule's own output; and resetAfter restarts the count after a long
run. These advanced combinators are covered in Combining schedules.
Retry failed flows
Use Flow.retry to rerun a flow after an expected domain failure (Cause.Fail).
Flow.retry doesn't retry defects (Cause.Die) or interruptions (Cause.Interrupt). Axial passes them through without consulting the schedule.
let callsMade = ref 0
let unstableCall : Flow<ClockEnvironment, string, unit> =
flow {
callsMade.Value <- callsMade.Value + 1
return! Flow.fail "temporary-error"
}
// Try up to 4 times: 1 initial attempt and 3 retries.
let resilientCall =
unstableCall
|> Flow.retry (Schedule.recurs 3)
callsMade: int refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
unstableCall: Flow<ClockEnvironment,string,unit>Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn 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
flow: FlowBuilderThe universal flow { } computation expression.
Value: intThe current value of the reference cell
(+): ^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.Flowfail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
resilientCall: Flow<ClockEnvironment,string,unit>(|>): '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
retry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
recurs: int -> Schedule<'env,'input,int>Creates a schedule that recurs a fixed number of times. counts the schedule's own decisions, not the total number of flow executions: Flow.retry and Flow.repeat always run the source flow once before consulting the schedule at all, so recurs 3 means 3 additional retries/repeats on top of that one free attempt: 4 executions in total, not 3. A Schedule value carries no state of its own (the attempt count lives in the retry/repeat call), so the same schedule value is safe to reuse across independent runs. The maximum number of additional times to recur, on top of the source flow's one free initial attempt. A schedule that recurs up to times, emitting the current attempt count (0 to n-1). let retryThreeTimes () = Schedule.recurs 3 // Flow.retry runs the source flow once for free, then consults the schedule at // attempts 0, 1, 2 (three retries) before giving up: 4 executions in total.
Requires & types
Requires: open Axial (packages: Axial)val callsMade: int refval unstableCall: Flow<ClockEnvironment,string,unit>val resilientCall: Flow<ClockEnvironment,string,unit>resilientCall |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Failure(Cause.Fail "temporary-error"))
callsMade.Value |> shouldEqual 4
resilientCall: Flow<ClockEnvironment,string,unit>(|>): '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
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
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.
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.
FailAn expected domain-specific failure.
callsMade: int refValue: intThe current value of the reference cell
Requires & types
Requires: open Axial; open Axial.PlatformService (packages: Axial, Axial.PlatformService)The retry stops when the flow succeeds or the schedule declines another run. If the schedule stops after a failure, the flow returns that failure.
Each attempt runs in its own child scope. When an attempt fails and another is scheduled, the failed attempt's finalizers run before the retry starts. The successful attempt's scope stays open until the enclosing scope closes, so a connection or handle it returns is still usable.
Retry only some errors
The schedule sees each typed error as its input. Schedule.whileInput continues only for errors that satisfy a
predicate, and Schedule.recursAtMost caps the number of retries of any schedule:
type FetchError =
| Unavailable
| RateLimited
| NotFound
/// A fetch that fails with each error in turn, then succeeds.
let fetchFailing (errors: FetchError list) : Flow<ClockEnvironment, FetchError, string> =
let remaining = ref errors
Flow.delay (fun () ->
match remaining.Value with
| error :: rest ->
remaining.Value <- rest
Flow.fail error
| [] -> Flow.ok "payload")
let isTransient error =
match error with
| Unavailable
| RateLimited -> true
| NotFound -> false
let resilientFetch (fetch: Flow<ClockEnvironment, FetchError, string>) =
fetch
|> Flow.retry (
Schedule.exponential (TimeSpan.FromMilliseconds 200.0)
|> Schedule.recursAtMost 3
|> Schedule.whileInput isTransient)
FsLiveDocsGeneratedPage2_10B692608C6D.FetchErrorUnavailableRateLimitedNotFoundfetchFailing: FetchError list -> Flow<ClockEnvironment,FetchError,string>errors: FetchError listlistThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
remaining: FetchError list refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
Axial.Flowdelay: (unit -> Flow<'env,'error,'value>) -> Flow<'env,'error,'value>Defers flow construction until execution time. A function that returns the flow to execute. Recursive calls deferred here are consumed by the executor without growing a chain of parent tasks. Work that must run after the recursive call still needs storage until that call finishes. A flow that lazily evaluates the factory when executed. let flow = Flow.delay (fun () -> Flow.succeed 42)
Value: FetchError listThe current value of the reference cell
error: FetchError(::)rest: FetchError listfail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
ok: 'value -> Flow<'env,'error,'value>Creates a successful synchronous flow. The value to wrap in a successful flow. A flow that always succeeds with the provided value.
isTransient: FetchError -> boolresilientFetch: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>fetch: Flow<ClockEnvironment,FetchError,string>(|>): '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
retry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
recursAtMost: int -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Stops a schedule after at most recurrences, keeping its output and delays. Like recurs, counts recurrences after the first run, so recursAtMost 3 with Flow.retry allows 4 executions in total. The maximum number of recurrences. The schedule to cap. Schedule.exponential (TimeSpan.FromMilliseconds 100.0) |> Schedule.recursAtMost 5
whileInput: ('input -> bool) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Continues only while the input satisfies a predicate. With Flow.retry the input is the typed error, so this selects which errors are worth retrying; an error that fails the predicate stops the schedule and the flow fails with it. With Flow.repeat the input is the successful value. Returns true for inputs that may recur. The schedule consulted for inputs that satisfy the predicate. Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.recursAtMost 3 |> Schedule.whileInput HttpError.isTransient
Requires & types
Requires: open Axial; open System (packages: Axial)val fetchFailing: FetchError list -> Flow<ClockEnvironment,FetchError,string>val isTransient: FetchError -> boolval resilientFetch: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>fetchFailing [ Unavailable; RateLimited ] |> resilientFetch |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success "payload")
fetchFailing [ Unavailable; NotFound ] |> resilientFetch |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Failure(Cause.Fail NotFound))
fetchFailing: FetchError list -> Flow<ClockEnvironment,FetchError,string>UnavailableRateLimited(|>): '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
resilientFetch: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>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
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
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.
NotFoundFailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
Requires & types
Requires: open Axial; open Axial.PlatformService (packages: Axial, Axial.PlatformService)Schedule.untilInput is the negation: it stops on the first input that satisfies the predicate.
Describe a retry with a record
A Retry record names the three choices most retries make, and Retry.schedule turns it into a schedule. Start
from Retry.defaults (3 retries, exponential backoff from 100 ms capped at 10 s, every error retried) and change
the fields you need:
let resilientFetchWithRecord (fetch: Flow<ClockEnvironment, FetchError, string>) =
fetch
|> Flow.retry (Retry.schedule { Retry.defaults with Retries = 5; When = isTransient; Backoff = Backoff.NoDelay })
resilientFetchWithRecord: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>fetch: Flow<ClockEnvironment,FetchError,string>Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
FsLiveDocsGeneratedPage2_10B692608C6D.FetchErrorstringAn abbreviation for the CLI type . Basic Types
(|>): '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.Flowretry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
Axial.RetryModuleConstructors for .
schedule: Retry<'input> -> Schedule<'env,'input,int>Builds the schedule a retry record describes. The retry description. A schedule that emits the retry number, starting at 0. Thrown when Retries is negative or a delay is negative.
Axial.Retry`1A retry described with named fields: how many retries, how long to wait, and which inputs to retry. Build one from Retry.defaults with record update syntax and pass it to Flow.retry through Retry.schedule. It is a shorthand for the common case; anything a record cannot express (jitter, elapsed-time limits, fixed-rate runs) is written as a Schedule directly. The input the retry decision sees: the typed error for Flow.retry, the defect for Flow.supervise.
defaults: Retry<'input>Three retries with exponential backoff from 100 ms, capped at 10 s, retrying every input. fetch |> Flow.retry (Retry.schedule { Retry.defaults with Retries = 5; When = HttpError.isTransient })
Retries: intThe maximum number of retries after the first attempt; 3 allows 4 executions in total.
When: 'input -> boolReturns true for inputs that should be retried; others stop immediately.
Backoff: BackoffThe delay before each retry.
Requires & types
Requires: open Axial (packages: Axial)val resilientFetchWithRecord: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>fetchFailing [ RateLimited; RateLimited; Unavailable ] |> resilientFetchWithRecord |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success "payload")
fetchFailing: FetchError list -> Flow<ClockEnvironment,FetchError,string>RateLimitedUnavailable(|>): '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
resilientFetchWithRecord: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>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
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
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.PlatformService (packages: Axial, Axial.PlatformService)Retries counts retries after the first attempt, the same as Schedule.recurs. Backoff is Backoff.NoDelay,
Backoff.Fixed delay, or Backoff.Exponential(initial, max). Write a Schedule pipeline when you need jitter,
fixed-rate timing, or elapsed-time limits.
Repeat successful flows
Use Flow.repeat to run a successful flow again. The schedule's input is the successful value.
let pollStatus : Flow<ClockEnvironment, Never, string> =
flow {
return "Still working"
}
// pollStatus has error type Never, so it cannot fail with a typed error. With a schedule that never stops,
// this repeats forever; the caller must interrupt it or let an outer scope end it.
let recurringPoll =
pollStatus
|> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0))
// A bounded variant: the initial run plus up to three repeats.
let pollFourTimes =
pollStatus
|> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0) |> Schedule.recursAtMost 3)
pollStatus: Flow<ClockEnvironment,Never,string>Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.NeverRepresents an error channel that cannot occur.
stringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
recurringPoll: Flow<ClockEnvironment,Never,string>(|>): '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.Flowrepeat: Schedule<'env,'value,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Repeats a successful flow according to a schedule. The flow runs once, and after each success the schedule sees the value and decides whether to run it again and how long to wait. Any failure stops the repetition immediately. When the schedule stops, the flow succeeds with the last value. Each run has its own child scope, closed before the next run starts, so a repetition that lasts the life of an application does not accumulate finalizers. Decides, from each value, whether to repeat and after what delay. The flow to repeat. A flow that succeeds with the value of the last run. heartbeat |> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0))
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
System.TimeSpanRepresents a time interval.
FromSeconds: float -> TimeSpanReturns a that represents a specified number of seconds, where the specification is accurate to the nearest millisecond. A number of seconds, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
pollFourTimes: Flow<ClockEnvironment,Never,string>recursAtMost: int -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Stops a schedule after at most recurrences, keeping its output and delays. Like recurs, counts recurrences after the first run, so recursAtMost 3 with Flow.retry allows 4 executions in total. The maximum number of recurrences. The schedule to cap. Schedule.exponential (TimeSpan.FromMilliseconds 100.0) |> Schedule.recursAtMost 5
Requires & types
Requires: open Axial; open System (packages: Axial)val pollStatus: Flow<ClockEnvironment,Never,string>val recurringPoll: Flow<ClockEnvironment,Never,string>val pollFourTimes: Flow<ClockEnvironment,Never,string>Flow.repeat consults the schedule only after a successful run. A typed failure, defect, or interruption stops the
repetition immediately; a schedule that never stops keeps repeating a successful flow until one of those happens or the
caller interrupts it.
The schedule receives each successful value as its input, so whileInput can stop the loop on the value itself:
let repeatCount = ref 0
let ticking : Flow<ClockEnvironment, Never, int> =
flow {
repeatCount.Value <- repeatCount.Value + 1
return repeatCount.Value
}
let repeatUntilThree =
ticking
|> Flow.repeat (Schedule.spaced TimeSpan.Zero |> Schedule.whileInput (fun value -> value < 3))
repeatCount: int refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
ticking: Flow<ClockEnvironment,Never,int>Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.NeverRepresents an error channel that cannot occur.
intAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
Value: intThe current value of the reference cell
(+): ^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"
repeatUntilThree: Flow<ClockEnvironment,Never,int>(|>): '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.Flowrepeat: Schedule<'env,'value,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Repeats a successful flow according to a schedule. The flow runs once, and after each success the schedule sees the value and decides whether to run it again and how long to wait. Any failure stops the repetition immediately. When the schedule stops, the flow succeeds with the last value. Each run has its own child scope, closed before the next run starts, so a repetition that lasts the life of an application does not accumulate finalizers. Decides, from each value, whether to repeat and after what delay. The flow to repeat. A flow that succeeds with the value of the last run. heartbeat |> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0))
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
System.TimeSpanRepresents a time interval.
Zero: TimeSpanRepresents the zero value. This field is read-only.
whileInput: ('input -> bool) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Continues only while the input satisfies a predicate. With Flow.retry the input is the typed error, so this selects which errors are worth retrying; an error that fails the predicate stops the schedule and the flow fails with it. With Flow.repeat the input is the successful value. Returns true for inputs that may recur. The schedule consulted for inputs that satisfy the predicate. Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.recursAtMost 3 |> Schedule.whileInput HttpError.isTransient
value: int(<): '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
Requires & types
Requires: open Axial; open System (packages: Axial)val repeatCount: int refval ticking: Flow<ClockEnvironment,Never,int>val repeatUntilThree: Flow<ClockEnvironment,Never,int>repeatCount.Value <- 0
repeatUntilThree |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success 3)
repeatCount.Value |> shouldEqual 3
repeatCount: int refValue: intThe current value of the reference cell
repeatUntilThree: Flow<ClockEnvironment,Never,int>(|>): '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
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
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.PlatformService (packages: Axial, Axial.PlatformService)A failure ends the loop immediately, without consulting the schedule again:
let failingRepeatCount = ref 0
let failingTick : Flow<ClockEnvironment, string, int> =
flow {
failingRepeatCount.Value <- failingRepeatCount.Value + 1
if failingRepeatCount.Value = 2 then return! Flow.fail "stopped" else return failingRepeatCount.Value
}
let failingRepeat = failingTick |> Flow.repeat (Schedule.spaced TimeSpan.Zero)
failingRepeatCount: int refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
failingTick: Flow<ClockEnvironment,string,int>Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
intAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
Value: intThe current value of the reference cell
(+): ^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"
(=): 'T -> 'T -> boolStructural equality The first parameter. The second parameter. The result of the comparison. 5 = 5 // Evaluates to true 5 = 6 // Evaluates to false [1; 2] = [1; 2] // Evaluates to true (1, 5) = (1, 6) // Evaluates to false
Axial.Flowfail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
failingRepeat: Flow<ClockEnvironment,string,int>(|>): '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
repeat: Schedule<'env,'value,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Repeats a successful flow according to a schedule. The flow runs once, and after each success the schedule sees the value and decides whether to run it again and how long to wait. Any failure stops the repetition immediately. When the schedule stops, the flow succeeds with the last value. Each run has its own child scope, closed before the next run starts, so a repetition that lasts the life of an application does not accumulate finalizers. Decides, from each value, whether to repeat and after what delay. The flow to repeat. A flow that succeeds with the value of the last run. heartbeat |> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0))
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
System.TimeSpanRepresents a time interval.
Zero: TimeSpanRepresents the zero value. This field is read-only.
Requires & types
Requires: open Axial; open System (packages: Axial)val failingRepeatCount: int refval failingTick: Flow<ClockEnvironment,string,int>val failingRepeat: Flow<ClockEnvironment,string,int>failingRepeatCount.Value <- 0
failingRepeat |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Failure(Cause.Fail "stopped"))
failingRepeatCount.Value |> shouldEqual 2
failingRepeatCount: int refValue: intThe current value of the reference cell
failingRepeat: Flow<ClockEnvironment,string,int>(|>): '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
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
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.
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.
FailAn expected domain-specific failure.
Requires & types
Requires: open Axial; open Axial.PlatformService (packages: Axial, Axial.PlatformService)Drive a stream from a schedule
FlowStream.fromSchedule turns a schedule into a stream of its outputs, each emitted after the delay the schedule
chooses. It ends when the schedule stops. With Schedule.fixedRate it is a tick stream that keeps to its grid even
when the consumer is slow: ticks the consumer was too busy to take are skipped, not delivered in a burst.
> open Axial.PlatformService;;
> (Schedule.fixedRate (System.TimeSpan.FromMilliseconds 20.0)
- |> FlowStream.fromSchedule
- |> FlowStream.take 3
- |> FlowStream.runCollect
- : Flow<ClockEnvironment, Never, int list>)
- |> Flow.run (ClockEnvironment Clock.live);;val it: Exit<int list,Never> = Success [0; 1; 2]AxialPlatformServiceAxial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
fixedRate: TimeSpan -> Schedule<'env,'input,int>Recurs at a fixed rate aligned to when the first run began, emitting the recurrence count. Runs start at start + n * period, so the time a run takes does not shift later runs. A run that takes longer than a period is followed by one immediate run, after which the schedule realigns to the next boundary; missed ticks are skipped rather than run in a burst. Thrown when is not positive. scanOnce |> Flow.repeat (Schedule.fixedRate (TimeSpan.FromMilliseconds 50.0))
SystemFromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
System.TimeSpanRepresents a time interval.
(|>): '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.FlowStreamModulefromSchedule: Schedule<'env,unit,'output> -> FlowStream<'env,'error,'output>Creates a stream that emits a schedule's outputs, each after the delay the schedule chooses. The stream ends when the schedule stops. Delays are measured on the runtime's time, and a slow consumer does not shift a fixed-rate schedule: fromSchedule (Schedule.fixedRate period) emits on the start + n * period grid, skipping ticks the consumer was too busy to take instead of bursting. // A tick every 50 ms, aligned to when the stream started. Schedule.fixedRate (TimeSpan.FromMilliseconds 50.0) |> FlowStream.fromSchedule
take: int -> FlowStream<'a,'b,'c> -> FlowStream<'a,'b,'c>Emits at most values. stream |> FlowStream.take 10
runCollect: FlowStream<'a,'b,'c> -> Flow<'a,'b,'c list>Collects all emitted values into a list. stream |> FlowStream.runCollect
Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.NeverRepresents an error channel that cannot occur.
intAn abbreviation for the CLI type . Basic Types
listThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
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
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
Schedule API reference
Schedule's full signature list — recurs, spaced, exponential, jitteredWith, fixedRate, union,
intersect, andThen, whileInput/untilInput, whileOutput/untilOutput, recursAtMost, upTo,
resetAfter, elapsed, map, plus Flow.retry, Flow.repeat, and FlowStream.fromSchedule — is in the
Schedule API reference.

