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.JavaScriptFibers
A Fiber is a handle to a running Flow. A flow describes work; a fiber represents its execution after it starts in the background.
Fiber-local values
FiberLocal.make initial creates an unnamed local. FiberLocal.named "jobId" initial creates one whose explicitly scoped value can be attached to log entries. FiberLocal.get local reads the current value or its initial value. FiberLocal.locally value local work overrides it for work and restores the prior value afterward. Child fibers inherit the value present when forked; concurrent siblings do not share changes. FiberLocal.current () synchronously returns only named locals with an explicitly scoped value, as (name, value) pairs. Unset defaults and unnamed locals are excluded.
The Mental Model
A Flow is cold until it starts. Forking starts it and returns a Fiber handle while the current workflow continues.
Show page setup
// Setup for the checked examples on this page.
open System
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 loadBoth left right =
flow {
let! leftFiber = Flow.fork left
let! rightValue = right
let! leftValue = Fiber.join leftFiber
return leftValue, rightValue
}
loadBoth: Flow<'a,'b,'c> -> Flow<'a,'b,'d> -> Flow<'a,'b,('c * 'd)>left: Flow<'a,'b,'c>right: Flow<'a,'b,'d>flow: FlowBuilderThe universal flow { } computation expression.
leftFiber: Fiber<'b,'c>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.
rightValue: 'dleftValue: 'cAxial.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 }
Requires & types
Requires: open Axial (packages: Axial)val loadBoth: Flow<'a,'b,'c> -> Flow<'a,'b,'d> -> Flow<'a,'b,('c * 'd)>loadBoth (Flow.ok 1 : Flow<ClockEnvironment, string, int>) (Flow.ok "two") |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success(1, "two"))
loadBoth: Flow<'a,'b,'c> -> Flow<'a,'b,'d> -> Flow<'a,'b,('c * 'd)>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.
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
(|>): '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
``.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)The example starts left in the background, runs right in the current workflow, then joins the child fiber before returning.
Structured Concurrency
Forked fibers retain a parent-child relationship: closing the parent scope interrupts running children and releases their scoped resources.
Fiber operations:
Flow.fork: starts a flow in the background and returns aFiber<'error, 'value>handle.Flow.forkResource: starts acquiring aResourcein the background; joining the fiber returns the resource, still open and owned by the caller's scope.Fiber.join: waits for the fiber and resumes with its successful value or typed failure.Fiber.await: waits for the fiber and returns itsExitwithout failing, for callers that decide what the outcome means.Fiber.poll: returns the fiber'sExitif it has settled, without waiting.Fiber.interrupt: asks the fiber to stop, then waits for the child workflow to report its finalExit.Flow.forkDetached: starts deliberate fire-and-forget work whose defects are never reported as unobserved.Flow.forkNamed: forks with a diagnostic name that carries into dumps and telemetry fiber spans, so long-lived background fibers are recognizable instead of bare ids.Fiber.dump: returns a diagnostic snapshot of one fiber handle.Flow.forkGracefulstop grace: forks a fiber that, when its scope closes, is asked to stop withstopand given up tograceto finish before it is interrupted. See stopping a consumer gracefully.
A fiber owns its scoped resources
A forked fiber runs in its own scope. The scoped resources
it acquires, such as a value from Flow.scopeResource or a hub subscription, are released when the fiber settles,
whether or not anything joins it. Fiber.join gives you the fiber's value, not its resources, so a resource the fiber
returns has already been released:
/// Wrong: the fiber releases the connection when it settles, before join returns it.
let joinForkedConnection (connection: Resource<ClockEnvironment, string, string>) =
flow {
let! fiber = Flow.scopeResource connection |> Flow.fork
return! Fiber.join fiber // already released
}
/// Right: only the acquisition runs in the fiber, and the caller's scope owns the connection.
let joinKeptConnection (connection: Resource<ClockEnvironment, string, string>) =
flow {
let! connecting = Flow.forkResource connection
return! Fiber.join connecting // still open
}
joinForkedConnection: Resource<ClockEnvironment,string,string> -> Flow<ClockEnvironment,string,string>connection: Resource<ClockEnvironment,string,string>Axial.Resource`3Describes acquisition of a value together with registration of its release in the current Flow scope. The release is a flow that cannot fail with a typed error. It runs without interruption, with the environment the value was acquired with; a defect it raises becomes a defect of the flow that closes the scope. The environment required to acquire the value. The typed acquisition failure. The acquired value.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
fiber: Fiber<string,string>Axial.FlowscopeResource: Resource<'env,'error,'value> -> Flow<'env,'error,'value>Acquires a described resource and registers its release with the current runtime scope. The resource is a scoped resource: it stays open until the current scope closes. Inside a forked fiber that is the fiber's own scope, which closes when the fiber settles; inside a branch of zipPar, race, sequencePar, or traversePar, the resource moves to the caller's scope if the caller receives the branch's value. Use forkResource to acquire it in the background and keep it. A flow that finishes acquiring after its scope has closed, such as a race loser, releases the resource at once and is interrupted. The acquisition and release description. A flow that succeeds with the acquired value.
(|>): '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
fork: 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.
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 }
joinKeptConnection: Resource<ClockEnvironment,string,string> -> Flow<ClockEnvironment,string,string>connecting: Fiber<string,string>forkResource: Resource<'env,'error,'value> -> Flow<'env,'none,Fiber<'error,'value>>Starts acquiring a resource in a new fiber, keeping the resource in the caller's scope. A fiber started with fork releases its scoped resources when it settles, so a resource it acquires is already released by the time Fiber.join returns it. forkResource runs only the acquisition in the fiber and registers the release with the caller's scope, so Fiber.join returns a live resource that is released when the caller's scope closes, as if the caller had used scopeResource. If the caller's scope closes before the acquisition finishes, the fiber is interrupted, and a resource that was acquired anyway is released straight away. Scoped resources that the acquiring flow itself registers along the way belong to the fiber and are released when it settles; only the described resource moves to the caller. To acquire several resources in parallel and use them together, use zipPar or sequencePar. The acquisition and release description. A flow that produces a fiber whose value is the acquired resource. flow { let! connecting = Flow.forkResource databaseConnection let! config = loadConfig let! connection = Fiber.join connecting // still open return! migrate connection config } |> Flow.scoped // the connection is released here
Requires & types
Requires: open Axial (packages: Axial)val joinForkedConnection: Resource<ClockEnvironment,string,string> -> Flow<ClockEnvironment,string,string>val joinKeptConnection: Resource<ClockEnvironment,string,string> -> Flow<ClockEnvironment,string,string>Whatever the fiber acquires — a value from Flow.scopeResource, a resource registered with
Flow.scopeAcquireRelease, a hub subscription, or a
scoped queue — is released when the fiber settles, not when the parent scope
eventually closes. A consumer fiber that ends therefore cannot leave a subscription behind that holds up a publisher.
Closing the parent scope interrupts fibers that are still running, then releases what they acquired.
Flow.forkResource differs: only the acquisition runs in the fiber, and the resource's release is registered with
the caller's scope. If that scope closes before the acquisition finishes, the fiber is interrupted and a resource
acquired anyway is released at once. To acquire several resources in parallel, use Flow.zipPar or
Flow.sequencePar; see Concurrency and scoped resources for
what each concurrent combinator does with scoped resources.
Joining, awaiting, or interrupting a fiber marks its outcome as observed. A fiber whose handle is simply discarded and that later dies with a defect is reported through the runtime's fiber observer; use Flow.forkDetached when the silence is intentional, and Flow.supervise to restart background work that dies with defects.
Why Fibers?
Compared with raw Task or Async values, fibers provide:
Interruption
In ordinary .NET code, cancellation often depends on manually threading a CancellationToken through every layer. In Axial, interruption is part of the execution model. Fiber.interrupt signals the child fiber and waits for it to finish, so callers can observe the final Exit<'value, 'error>.
Typed Outcomes
A Fiber<'error, 'value> remembers the error type and success type of the workflow it is running. When you Fiber.join a fiber, the joined flow has the same typed failure channel as the child.
Clear Ownership
Fibers make background work visible in the workflow that started it. If the parent needs the result, it joins. If the parent no longer needs the result, it interrupts. An untracked task, by contrast, fails unnoticed unless some other layer checks it.
Diagnostics
Every forked fiber carries metadata:
FiberId: A unique runtime id for the child fiber.Name: The diagnostic name fromFlow.forkNamed, if one was given.ParentId: The id of the fiber that calledFlow.fork.Annotations: The runtime annotations (Flow.annotate) in scope at the fork site.StartedAt/SettledAt: UTC timestamps for fork and settle.Status:Running,Succeeded,Failed, orInterrupted.
Use Fiber.dump when logging or debugging one fiber. The dump is a snapshot, so a running fiber can report Running before Fiber.join and Succeeded, Failed, or Interrupted afterward. To see every live fiber at once as a parent/child tree, install a FiberRegistry with Flow.withFiberRegistry and call registry.DumpAt(clock); see Observability.
Underlying Implementation
On .NET, a fiber wraps a Task<Exit<'value, 'error>>, a CancellationTokenSource, and diagnostic metadata. On Fable, it wraps an Async<Exit<'value, 'error>> with the same public model. Only Metadata is public; the task and cancellation source are reached through the Fiber functions, so observation and interruption always go through the runtime's bookkeeping.
Concurrency Primitives
Use parallel combinators when they express the whole relationship:
Flow.zipPar: Runs two flows concurrently and waits for both.Flow.race: Runs two flows concurrently and returns the result of the winner, interrupting the loser.Flow.traversePar: Maps many values with bounded concurrency and returns the results in input order.Flow.forEachPar: Runs a flow for each value with bounded concurrency, discarding the results.
Unlike Flow.fork, these return values, and scoped resources come with the values: what a flow acquires moves to the
caller's scope when you receive its value, and is released when you don't (a race loser, or every branch of a
zipPar or traversal that failed). So Flow.zipPar (Flow.scopeResource a) (Flow.scopeResource b) opens two resources
in parallel and both stay open until the caller's scope closes.
let fetchPage (url: string) : Flow<ClockEnvironment, string, int> =
Flow.sleep (TimeSpan.FromMilliseconds 1.0) |> Flow.map (fun () -> url.Length)
let pageSizes (urls: string list) : Flow<ClockEnvironment, string, int list> =
urls |> Flow.traversePar (Parallelism.bounded 8) fetchPage
let indexFile (indexed: Ref<string list>) (file: string) : Flow<ClockEnvironment, string, unit> =
indexed |> Ref.update (fun files -> file :: files)
/// Indexes every file in parallel and returns the indexed files, sorted.
let indexAll (files: string list) : Flow<ClockEnvironment, string, string list> =
flow {
let! indexed = Ref.make []
do! files |> Flow.forEachPar (Parallelism.ofProcessors id) (indexFile indexed)
let! indexedFiles = Ref.get indexed
return List.sort indexedFiles
}
fetchPage: string -> Flow<ClockEnvironment,string,int>url: stringstringAn abbreviation for the CLI type . Basic Types
Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
intAn abbreviation for the CLI type . Basic Types
Axial.Flowsleep: TimeSpan -> Flow<'env,'error,unit>Suspends the flow for the specified duration, observing cancellation. The duration to sleep. A flow that completes after the specified delay, or is interrupted if cancelled first.
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
map: ('value -> 'next) -> Flow<'env,'error,'value> -> Flow<'env,'error,'next>Transforms the successful value of a flow. If the source fails, the is not executed. The original failure cause is preserved, including typed failures, interruption, and defects. Use map for pure value transformations after an effect has succeeded. A function of type 'value -> 'next to transform the successful value. The source flow of type to transform. A new with the transformed success value of type 'next. let flow = Flow.succeed 1 |> Flow.map (fun x -> x + 1)
Length: intGets the number of characters in the current object. The number of characters in the current string.
pageSizes: string list -> Flow<ClockEnvironment,string,int list>urls: string 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.
traversePar: Parallelism -> ('value -> Flow<'env,'error,'next>) -> 'value seq -> Flow<'env,'error,'next list>Maps values to flows and runs them with bounded concurrency, returning results in input order. At most mappings run at once; as each finishes, its worker starts the next value. The first failure interrupts the mappings still running and waits for their cleanup before the flow fails, so no sibling is left running in the background. Size CPU-bound work with Parallelism.ofProcessors. When each worker needs its own connection or handle, use traverseParUsing. Scoped resources follow sequencePar: when every mapping succeeds, what each one acquired moves to the caller's scope; when any fails, all of it is released before the traversal returns. The maximum number of mappings running at once. Maps each value to a flow. The values to map. A flow containing the mapped values in the order of . let! pages = urls |> Flow.traversePar (Parallelism.bounded 8) fetchPage
Axial.ParallelismModuleCreates bounds for parallel Flow and stream operators.
bounded: int -> ParallelismCreates a positive concurrency bound. Thrown when is not positive.
indexFile: Ref<string list> -> string -> Flow<ClockEnvironment,string,unit>indexed: Ref<string list>Axial.State.Ref`1Represents a handle to a mutable reference that can be updated atomically. The type of the value stored in the reference.
file: stringunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
Axial.State.RefModuleupdate: ('T -> 'T) -> Ref<'T> -> Flow<'env,'none,unit>Updates the value of the reference using the supplied function. The update function of type 'T -> 'T. The to update. A flow that updates the value and returns unit. Ref.update (fun x -> x + 1) myRef
files: string list(::)indexAll: string list -> Flow<ClockEnvironment,string,string list>flow: FlowBuilderThe universal flow { } computation expression.
make: 'T -> Flow<'env,'none,Ref<'T>>Creates a new with the initial value. The initial value of the reference. A flow that creates and returns the reference. (Ref.make 10).RunSynchronously(())
forEachPar: Parallelism -> ('value -> Flow<'env,'error,unit>) -> 'value seq -> Flow<'env,'error,unit>Runs a flow for each value with bounded concurrency, discarding the results. Runs like traversePar: at most at once, the first failure interrupts the rest, and scoped resources move to the caller's scope only when every flow succeeds. The maximum number of flows running at once. The flow to run for each value. The values to process. do! files |> Flow.forEachPar (Parallelism.ofProcessors id) indexFile
ofProcessors: (int -> int) -> ParallelismSizes a concurrency bound from the number of processors available to the process. Use this for CPU-bound work. The result is clamped to at least 1, so fun n -> n / 2 is safe on a single-core machine. The processor count is read once, when this is called. On JavaScript the count is 1. Computes the bound from the processor count. files |> Flow.traversePar (Parallelism.ofProcessors id) hashFile
id: 'T -> 'TThe identity function The input value. The same value. id 12 // Evaluates to 12 id "abc" // Evaluates to "abc"
indexedFiles: string listget: Ref<'T> -> Flow<'env,'none,'T>Reads the current value of the reference. The to read from. A flow that returns the current value. let readInitialValue () = flow { let! reference = Ref.make 10 return! Ref.get reference }
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
sort: 'T list -> 'T listSorts the given list using . This is a stable sort, i.e. the original order of equal elements is preserved. This is an O(n log n) operation, where n is the length of the list. The input list. The sorted list. let input = [8; 4; 3; 1; 6; 1] List.sort input Evaluates to [1; 1; 3; 4; 6; 8].
Requires & types
Requires: open Axial; open Axial.State; open System (packages: Axial)val fetchPage: string -> Flow<ClockEnvironment,string,int>val pageSizes: string list -> Flow<ClockEnvironment,string,int list>val indexFile: Ref<string list> -> string -> Flow<ClockEnvironment,string,unit>val indexAll: string list -> Flow<ClockEnvironment,string,string list>pageSizes [ "a"; "bb"; "ccc" ] |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success [ 1; 2; 3 ])
indexAll [ "b.fs"; "a.fs"; "c.fs" ] |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success [ "a.fs"; "b.fs"; "c.fs" ])
pageSizes: string list -> Flow<ClockEnvironment,string,int list>(|>): '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.
indexAll: string list -> Flow<ClockEnvironment,string,string list>Requires & types
Requires: open Axial; open Axial.PlatformService (packages: Axial, Axial.PlatformService)traversePar returns results in input order, whatever order the work finishes in.
At most the given number of flows run at once, and each worker starts the next value as soon as it finishes one. The first failure interrupts the flows still running and waits for their cleanup, so no sibling keeps running after the traversal has failed. Size CPU-bound work with Parallelism.ofProcessors, which clamps to at least 1.
When each worker needs its own connection or handle, use Flow.traverseParUsing or Flow.forEachParUsing with a
Resource. Each worker acquires the resource once when it starts, reuses it for every value it takes, and releases it
when it finishes or the traversal fails, so at most parallelism resources exist at once:
/// Counts readers opened and closed.
type ReaderCounts = { Opened: int; Closed: int }
let openReader (counts: Ref<ReaderCounts>) : Resource<ClockEnvironment, string, int> =
Resource.create
(counts |> Ref.updateAndGet (fun c -> { c with Opened = c.Opened + 1 }) |> Flow.map _.Opened)
(fun _ -> counts |> Ref.update (fun c -> { c with Closed = c.Closed + 1 }))
let searchCommit (reader: int) (commit: int) : Flow<ClockEnvironment, string, bool> = Flow.ok (commit % 2 = 0)
/// Searches the commits with at most two readers, and returns the results with the reader counts.
let evenCommits (commits: int list) : Flow<ClockEnvironment, string, bool list * ReaderCounts> =
flow {
let! counts = Ref.make { Opened = 0; Closed = 0 }
let! results = commits |> Flow.traverseParUsing (Parallelism.bounded 2) (openReader counts) searchCommit
let! final = Ref.get counts
return results, final
}
FsLiveDocsGeneratedPage10_F9357565CFEE.ReaderCountsOpened: intintAn abbreviation for the CLI type . Basic Types
Closed: intopenReader: Ref<ReaderCounts> -> Resource<ClockEnvironment,string,int>counts: Ref<ReaderCounts>Axial.State.Ref`1Represents a handle to a mutable reference that can be updated atomically. The type of the value stored in the reference.
Axial.Resource`3Describes acquisition of a value together with registration of its release in the current Flow scope. The release is a flow that cannot fail with a typed error. It runs without interruption, with the environment the value was acquired with; a defect it raises becomes a defect of the flow that closes the scope. The environment required to acquire the value. The typed acquisition failure. The acquired value.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ResourceModulecreate: Flow<'env,'error,'value> -> ('value -> Flow<'env,Never,unit>) -> Resource<'env,'error,'value>Describes acquisition together with a release flow registered in the current Flow scope. The release runs when the scope closes, without interruption and with the environment captured at acquisition. It cannot fail with a typed error; a defect it raises becomes a defect of the flow that closes the scope, after the scope's other releases have run. Resource.create openConnection (fun connection -> Flow.delay (fun () -> connection.Close(); Flow.succeed ()))
(|>): '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.RefModuleupdateAndGet: ('T -> 'T) -> Ref<'T> -> Flow<'env,'none,'T>Updates the value using the supplied function and returns the value after the update. The update function of type 'T -> 'T. The to update. A flow that returns the updated value. Ref.updateAndGet (fun x -> x + 1) myRef
c: ReaderCountsAxial.Flowmap: ('value -> 'next) -> Flow<'env,'error,'value> -> Flow<'env,'error,'next>Transforms the successful value of a flow. If the source fails, the is not executed. The original failure cause is preserved, including typed failures, interruption, and defects. Use map for pure value transformations after an effect has succeeded. A function of type 'value -> 'next to transform the successful value. The source flow of type to transform. A new with the transformed success value of type 'next. let flow = Flow.succeed 1 |> Flow.map (fun x -> x + 1)
_arg1: ReaderCountsupdate: ('T -> 'T) -> Ref<'T> -> Flow<'env,'none,unit>Updates the value of the reference using the supplied function. The update function of type 'T -> 'T. The to update. A flow that updates the value and returns unit. Ref.update (fun x -> x + 1) myRef
searchCommit: int -> int -> Flow<ClockEnvironment,string,bool>reader: intcommit: intAxial.Flow`3boolAn abbreviation for the CLI type . Basic Types
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.
(%): ^T1 -> ^T2 -> ^T3Overloaded modulo operator The first parameter. The second parameter. The result of the operation. 29 % 5 // Evaluates to 4
(=): '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
evenCommits: int list -> Flow<ClockEnvironment,string,(bool list * ReaderCounts)>commits: int 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.
flow: FlowBuilderThe universal flow { } computation expression.
make: 'T -> Flow<'env,'none,Ref<'T>>Creates a new with the initial value. The initial value of the reference. A flow that creates and returns the reference. (Ref.make 10).RunSynchronously(())
results: bool listtraverseParUsing: Parallelism -> Resource<'env,'error,'resource> -> ('resource -> 'value -> Flow<'env,'error,'next>) -> 'value seq -> Flow<'env,'error,'next list>Like traversePar, giving each worker its own resource, acquired once and reused for every value it maps. Each worker acquires when it starts and releases it when it has no values left, or when the traversal fails or is interrupted. At most resources exist at once. Use it for work that needs an expensive handle per thread of work, such as a database connection or a repository reader that is not safe to share. The worker's resource never reaches the caller; other scoped resources the mapping acquires behave as in traversePar. The maximum number of workers, and so of resources. The resource each worker acquires. Maps a value to a flow, given the worker's resource. The values to map. A flow containing the mapped values in the order of . let! matches = commits |> Flow.traverseParUsing (Parallelism.ofProcessors id) openReader (fun reader commit -> search reader commit)
Axial.ParallelismModuleCreates bounds for parallel Flow and stream operators.
bounded: int -> ParallelismCreates a positive concurrency bound. Thrown when is not positive.
final: ReaderCountsget: Ref<'T> -> Flow<'env,'none,'T>Reads the current value of the reference. The to read from. A flow that returns the current value. let readInitialValue () = flow { let! reference = Ref.make 10 return! Ref.get reference }
Requires & types
Requires: open Axial; open Axial.State (packages: Axial)val openReader: Ref<ReaderCounts> -> Resource<ClockEnvironment,string,int>val searchCommit: int -> int -> Flow<ClockEnvironment,string,bool>val evenCommits: int list -> Flow<ClockEnvironment,string,(bool list * ReaderCounts)>match evenCommits [ 1..6 ] |> Flow.run (ClockEnvironment Clock.live) with
| Exit.Success(results, counts) ->
results |> shouldEqual [ false; true; false; true; false; true ]
counts.Opened <= 2 |> shouldEqual true
counts.Closed |> shouldEqual counts.Opened
| other -> failwithf "Expected success, got %A" other
evenCommits: int list -> Flow<ClockEnvironment,string,(bool list * ReaderCounts)>(..): ^T -> ^T -> ^T seqThe standard overloaded range operator, e.g. [n..m] for lists, seq {n..m} for sequences The start value of the range. The end value of the range. The sequence spanning the range. [1..4] // Evaluates to [1; 2; 3; 4] [1.5..4.4] // Evaluates to [1.5; 2.5; 3.5] ['a'..'d'] // Evaluates to ['a'; 'b'; 'c'; 'd'] [|1..4|] // Evaluates to an array [|1; 2; 3; 4|] { 1..4 } // Evaluates to a sequence [1; 2; 3; 4])
(|>): '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.
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.
results: bool listcounts: ReaderCountsshouldEqual: 'a -> 'a -> unitOpened: int(<=): 'T -> 'T -> boolStructural less-than-or-equal comparison The first parameter. The second parameter. The result of the comparison. 5 <= 1 // Evaluates to false 5 <= 5 // Evaluates to true [1; 5] <= [1; 6] // Evaluates to true
Closed: intother: Exit<(bool list * ReaderCounts),string>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.PlatformService (packages: Axial, Axial.PlatformService)FlowStream.mapFlowParUsing does the same for a stream, lending each running mapping a resource from a pool of at
most parallelism.
Use explicit fibers when the parent workflow needs to start child work, do something else, and decide later whether to join or interrupt it.
Latest Wins
When only the newest request matters, such as a search box or autocomplete, hold the running fiber in a FiberSlot and
fork with Flow.forkReplacing. Each fork signals the previous fiber in the slot to stop and does not wait for it, so
the new request starts at once:
let search (query: string) : Flow<ClockEnvironment, string, string> =
Flow.sleep (TimeSpan.FromMilliseconds 50.0) |> Flow.map (fun () -> $"results for {query}")
/// Forks a search per keystroke into one slot, and reports the last result and whether each earlier search stopped.
let typeAhead (queries: string list) : Flow<ClockEnvironment, string, string * bool list> =
flow {
let! slot = FiberSlot.make ()
let! searches = queries |> Flow.traverse (fun query -> search query |> Flow.forkReplacing slot)
let! last = searches |> List.last |> Fiber.join
let! earlier = searches |> List.take (searches.Length - 1) |> Flow.traverse Fiber.await
return last, earlier |> List.map (fun exit -> match exit with Exit.Failure cause -> Cause.isInterrupted cause | _ -> false)
}
search: string -> Flow<ClockEnvironment,string,string>query: stringstringAn abbreviation for the CLI type . Basic Types
Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.Flowsleep: TimeSpan -> Flow<'env,'error,unit>Suspends the flow for the specified duration, observing cancellation. The duration to sleep. A flow that completes after the specified delay, or is interrupted if cancelled first.
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
map: ('value -> 'next) -> Flow<'env,'error,'value> -> Flow<'env,'error,'next>Transforms the successful value of a flow. If the source fails, the is not executed. The original failure cause is preserved, including typed failures, interruption, and defects. Use map for pure value transformations after an effect has succeeded. A function of type 'value -> 'next to transform the successful value. The source flow of type to transform. A new with the transformed success value of type 'next. let flow = Flow.succeed 1 |> Flow.map (fun x -> x + 1)
typeAhead: string list -> Flow<ClockEnvironment,string,(string * bool list)>queries: string 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.
boolAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
slot: FiberSlot<string,string>Axial.FiberSlotModuleCreates and clears and values.
make: unit -> Flow<'env,'none,FiberSlot<'error,'value>>Creates an empty slot for Flow.forkReplacing.
searches: Fiber<string,string> listtraverse: ('value -> Flow<'env,'error,'next>) -> 'value seq -> Flow<'env,'error,'next list>Transforms a sequence of values into a flow and stops at the first failure. A function that maps each value to a flow. The sequence of values to transform. A flow containing a list of the successful mapped values. let flows = [1; 2; 3] |> Flow.traverse (fun x -> Flow.succeed (x * 2))
forkReplacing: FiberSlot<'error,'value> -> Flow<'env,'error,'value> -> Flow<'env,'none,Fiber<'error,'value>>Starts a flow in a new fiber held by , interrupting the fiber it replaces. Latest wins: the previous fiber in the slot is signalled to stop and is not waited for, so the new request starts at once. Its outcome is marked observed. Use this for UI requests where a result for stale input is worthless, such as search-as-you-type. The slot that holds the current fiber. The flow to run. A flow that produces the new fiber's handle. let onQueryChanged slot query = search query |> Flow.forkReplacing slot |> Flow.ignore
last: stringMicrosoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
last: 'T list -> 'TReturns the last element of the list. The input list. The last element of the list. Thrown when the input does not have any elements. [ "pear"; "banana" ] |> List.last Evaluates to banana [ ] |> List.last Throws ArgumentException Lists are represented as linked lists so this is an O(n) operation, where n is the length of the list.
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 }
earlier: Exit<string,string> listtake: int -> 'T list -> 'T listReturns the first N elements of the list. Throws InvalidOperationException if the count exceeds the number of elements in the list. List.truncate returns as many items as the list contains instead of throwing an exception. This is an O(count) operation. The number of items to take. The input list. The result list. Thrown when the input list is empty. Thrown when count exceeds the number of elements in the list. let inputs = ["a"; "b"; "c"; "d"] inputs |> List.take 2 Evaluates to ["a"; "b"] let inputs = ["a"; "b"; "c"; "d"] inputs |> List.take 6 Throws InvalidOperationException. let inputs = ["a"; "b"; "c"; "d"] inputs |> List.take 0 Evaluates to the empty list.
Length: intGets the number of items contained in the list
(-): ^T1 -> ^T2 -> ^T3Overloaded subtraction operator The first parameter. The second parameter. The result of the operation. 10 - 2 // Evaluates to 8
await: Fiber<'error,'value> -> Flow<'env,'none,Exit<'value,'error>>Waits for a fiber and returns its , never failing. Use this when the caller decides what the fiber's outcome means, for example a cache that shares one computation between callers, or a supervisor that restarts children. Unlike Deferred.await, which returns the completed value, this returns the whole exit. Interrupting the awaiting flow stops the wait, not the fiber. The fiber to wait for. A flow that succeeds with the fiber's exit. flow { match! Fiber.await fiber with | Exit.Success value -> return Some value | Exit.Failure _ -> return None }
map: ('T -> 'U) -> 'T list -> 'U listBuilds a new collection whose elements are the results of applying the given function to each of the elements of the collection. The function to transform elements from the input list. The input list. The list of transformed elements. let inputs = [ "a"; "bbb"; "cc" ] inputs |> List.map (fun x -> x.Length) Evaluates to [ 1; 3; 2 ] This is an O(n) operation, where n is the length of the list.
exit: Exit<string,string>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.
cause: Cause<string>Axial.CauseisInterrupted: Cause<'error> -> boolReturns whether the cause tree contains an interruption signal.
Requires & types
Requires: open Axial; open System (packages: Axial)val search: string -> Flow<ClockEnvironment,string,string>val typeAhead: string list -> Flow<ClockEnvironment,string,(string * bool list)>typeAhead [ "a"; "ax"; "axi" ] |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success("results for axi", [ true; true ]))
typeAhead: string list -> Flow<ClockEnvironment,string,(string * bool list)>(|>): '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)Flow.forkReplacingKey key slots does the same per key, with a slot from FiberSlot.makeKeyed, so a new preview for
one document replaces only that document's previous load. A key's entry is removed when its fiber settles.
FiberSlot.interrupt and FiberSlot.interruptAll stop what is running and wait for its cleanup, for example on
shutdown. FiberSlot.count returns how many keyed fibers are running. For a stream of inputs,
FlowStream.switchMapFlow applies the same rule inside the stream.
Stopping a consumer gracefully
Interrupting a consumer the moment its scope closes throws away its backlog. Flow.forkGraceful changes what closing
the scope does: it runs a stop request, waits up to a grace period for the fiber to finish, and interrupts it only if it
is still running after that. For a queue consumer, the stop request is Dequeue.shutdown: the consumer's stream then
ends normally once it has drained the queue.
> open Axial.PlatformService;;
> (flow {
- let written = ResizeArray<int>()
- do!
- flow {
- let! (samples: Queue<int>) = Queue.bounded 100
- let! _ =
- samples
- |> FlowStream.fromDequeue
- |> FlowStream.runForEach written.Add
- |> Flow.forkGraceful (Dequeue.shutdown samples) (System.TimeSpan.FromSeconds 5.0)
- do! samples |> Queue.offerAll [ 1..5 ] |> Flow.ignore
- }
- |> Flow.scoped
- return List.ofSeq written
- } : Flow<ClockEnvironment, Never, int list>)
- |> Flow.run (ClockEnvironment Clock.live);;val it: Exit<int list,Never> = Success [1; 2; 3; 4; 5]AxialPlatformServiceflow: FlowBuilderThe universal flow { } computation expression.
written: ResizeArray<int>``.ctor``: unit -> unitInitializes a new instance of the class that is empty and has the default initial capacity.
intAn abbreviation for the CLI type . Basic Types
samples: Queue<int>Axial.Queue`1An asynchronous FIFO queue for handing values between fibers, with an overflow strategy chosen at creation. Create one with Queue.make or a shorthand such as Queue.bounded, offer with Queue.offer, and take with the Dequeue functions. A queue is meant for one logical consumer; use Hub when zero or many consumers each need every value. The type of the queued values.
Axial.QueueModuleCreates queues and offers values to them. Take values, inspect, and shut a queue down with the Dequeue functions.
bounded: int -> Flow<'env,'error,Queue<'a>>Creates a lossless queue whose offer suspends while it holds values. Shorthand for Queue.make (QueueStrategy.BackPressure capacity). Queue.bounded 64
(|>): '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.FlowStreamModulefromDequeue: Dequeue<'value> -> FlowStream<'env,'error,'value>Creates a stream that takes values from a queue or hub subscription until it is shut down and drained. Shutdown is the normal end of the stream, not a failure: after Dequeue.shutdown or Hub.shutdown the stream emits the remaining backlog and then completes. Each pull suspends while the queue is empty. Interrupting a pull leaves any value it would have received in the queue. flow { let! (jobs: Queue<string>) = Queue.bounded 8 do! jobs |> Queue.offerAll [ "a"; "b" ] |> Flow.ignore do! Dequeue.shutdown jobs return! jobs |> FlowStream.fromDequeue |> FlowStream.runCollect }
runForEach: ('value -> unit) -> FlowStream<'env,'error,'value> -> Flow<'env,'error,unit>Executes the stream and performs a synchronous action for each successful value. The function to execute for each value emitted by the stream. The stream to execute. A flow that represents the execution of the stream. If the stream fails, the flow fails with the same cause. FlowStream.fromSeq ["a"; "b"; "c"] |> FlowStream.runForEach (printfn "%s") |> Flow.run ()
Add: int -> unitAdds an object to the end of the . The object to be added to the end of the . The value can be for reference types.
Axial.FlowforkGraceful: Flow<'env,Never,unit> -> TimeSpan -> Flow<'env,'error,'value> -> Flow<'env,'none,Fiber<'error,'value>>Starts a flow in a new fiber that, when its forking scope closes, is asked to stop and given time to finish before it is interrupted. A plain fork is interrupted as soon as its scope closes. A consumer draining a queue needs the opposite: stop taking new work, finish the backlog, then end. When the forking scope closes, forkGraceful runs , waits up to for the fiber to finish on its own, and only then interrupts it. For a queue consumer, stop is usually Dequeue.shutdown: the consumer's stream ends normally once it has drained the backlog. The fiber is not interrupted when the forking flow is interrupted, only when its scope closes after the stop and grace period. Fiber.interrupt still interrupts it immediately. A defect in stop is reported as a cleanup defect of the closing scope. Asks the fiber to finish, for example by shutting down the queue it consumes. How long to wait after before interrupting the fiber. The flow to fork. flow { let! (samples: Queue<float>) = Queue.bounded 1000 let! _ = samples |> FlowStream.fromDequeue |> FlowStream.runForEachFlow writeSample |> Flow.forkGraceful (Dequeue.shutdown samples) (TimeSpan.FromSeconds 5.0) do! samples |> Queue.offer 21.5 |> Flow.ignore } |> Flow.scoped // closing the scope flushes every offered sample before returning
Axial.DequeueModuleTakes values from a , inspects it, and shuts it down. Every function here also accepts a .
shutdown: Dequeue<'a> -> Flow<'env,'error,unit>Shuts the queue down. Calling it again has no effect. Either side may shut a queue down. On a hub subscription this unsubscribes: the hub stops delivering to it, and a publisher waiting on it moves on.
SystemFromSeconds: 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 .
System.TimeSpanRepresents a time interval.
offerAll: 'a seq -> Queue<'a> -> Flow<'env,'error,bool>Adds values in order according to the queue's strategy. true when every value was accepted. A dropping queue accepts values while it has room and returns false if it discarded any. A bounded queue suspends as needed; values from other producers may be interleaved between the batch's values, but the batch's own order is kept. If the flow is interrupted part way, the values already accepted stay in the queue. flow { let! (samples: Queue<float>) = Queue.unbounded () do! samples |> Queue.offerAll [ 1.0; 2.0; 3.0 ] |> Flow.ignore return! samples |> Dequeue.takeUpTo 500 }
(..): ^T -> ^T -> ^T seqThe standard overloaded range operator, e.g. [n..m] for lists, seq {n..m} for sequences The start value of the range. The end value of the range. The sequence spanning the range. [1..4] // Evaluates to [1; 2; 3; 4] [1.5..4.4] // Evaluates to [1.5; 2.5; 3.5] ['a'..'d'] // Evaluates to ['a'; 'b'; 'c'; 'd'] [|1..4|] // Evaluates to an array [|1; 2; 3; 4|] { 1..4 } // Evaluates to a sequence [1; 2; 3; 4])
ignore: Flow<'env,'error,'value> -> Flow<'env,'error,unit>Maps the successful value of a synchronous flow to unit. The source flow. A flow that succeeds with unit instead of the original value. let flow = Flow.succeed 42 |> Flow.ignore
scoped: Flow<'env,'error,'value> -> Flow<'env,'error,'value>Runs a flow in a child scope and closes that scope before returning. Resources and fibers acquired inside the flow are released after success, typed failure, defect, or interruption without waiting for the surrounding application scope to close.
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
ofSeq: 'T seq -> 'T listBuilds a new list from the given enumerable object. The input sequence. The list of elements from the sequence. let inputs = seq { 1; 2; 5 } inputs |> List.ofSeq Evaluates to [ 1; 2; 5 ]. This is an O(n) operation, where n is the length of the sequence.
Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.NeverRepresents an error channel that cannot occur.
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.
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.
The scope closed right after the offers, before the consumer had necessarily taken any of them. Closing it shut the
queue down, and the consumer finished all five values before the scope finished closing. With a plain Flow.fork, the
consumer would have been interrupted with part of the backlog still queued.
The pipeline torture test stops a control loop and a historian this way and checks that no sample is lost.
A graceful fiber is also not interrupted when the flow that forked it is interrupted: its stop request runs when that
flow's scope closes, so a consumer still flushes when the application is cancelled. Fiber.interrupt still interrupts
it immediately.

