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.JavaScriptScopes
.NET already has resources, and F# already has use and use!. Axial's flow { } computation expression supports
both directly:
Shared setup
// Setup for the checked examples on this page.
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.JavaScript
/// 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 actual
SystemIOThreadingTasksAxialLayersConsoleFileSystemHostingBrowserNodePlatformServiceStateTelemetryJavaScriptshouldEqual: 'a -> 'a -> unitexpected: 'aactual: 'a(<>): 'T -> 'T -> boolStructural inequality The first parameter. The second parameter. The result of the comparison. 5 <> 5 // Evaluates to false 5 <> 6 // Evaluates to true [1; 2] <> [1; 2] // Evaluates to false
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.
let readFirstLine path =
flow {
use reader = File.OpenText path
return! ColdTask(fun _ -> reader.ReadLineAsync())
}
readFirstLine: string -> Flow<'a,'b,string>path: stringflow: FlowBuilderThe universal flow { } computation expression.
reader: StreamReaderSystem.IO.FileProvides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of objects.
OpenText: string -> StreamReaderOpens an existing UTF-8 encoded text file for reading. The file to be opened for reading. A on the specified path. The caller does not have the required permission. is a zero-length string, contains only white space, or contains one or more invalid characters as defined by . is . The specified path, file name, or both exceed the system-defined maximum length. The specified path is invalid, (for example, it is on an unmapped drive). The file specified in was not found. is in an invalid format.
ColdTaskReadLineAsync: unit -> Task<string>Reads a line of characters asynchronously from the current stream and returns the data as a string. A task that represents the asynchronous read operation. The value of the parameter contains the next line from the stream, or is if all the characters have been read. The number of characters in the next line is larger than . The stream has been disposed. The reader is currently in use by a previous read operation.
This is a lexical lifetime. The compiler disposes reader when control leaves the body governed by that use. In a
flow { }, disposal happens before the Flow produced by that body completes. The resource cannot safely escape for
another function or later workflow to use.
A Flow scope is a runtime ownership boundary. It lets resources and child fibers live across function and subflow calls, then closes all of them together after success, failure, interruption, or defect.
Lifetime maps
The diagrams use containment literally: Flow B is a subflow inside Flow A, and a resource bar sits inside the boundary that owns it.
Lexical use ends with its subflow body
Flow B owns reader lexically. When Flow B returns to Flow A, reader has already been disposed.
Registration in the current scope outlives Flow B
Flow B ends, but R does not. Flow.scopeResource and the other scope... functions register with the current scope,
so R remains alive while Flow A continues and is cleaned only when that scope closes.
Flow.scoped creates an earlier cleanup boundary
Here Flow B still ends before R does, but the child scope closes before Flow A ends. This is the reason for
Flow.scoped: it chooses a runtime cleanup boundary independently of the function or subflow that acquired the
resource.
Create a local runtime scope
/// A stand-in connection that records when it is opened and closed.
let lifecycle = ResizeArray<string>()
type Connection(name: string) =
member _.Name = name
let openConnection : Flow<string, Connection> =
Flow.delay (fun () ->
lifecycle.Add "open"
Flow.ok (Connection "orders"))
let closeConnection (connection: Connection) (_: CancellationToken) : Task =
lifecycle.Add $"close {connection.Name}"
Task.CompletedTask
let runApplicationWork (connection: Connection) : Flow<string, string> =
Flow.delay (fun () ->
lifecycle.Add "work"
Flow.ok $"used {connection.Name}")
let application : Flow<string, string> =
Flow.scoped (
flow {
let! connection =
Flow.scopeAcquireRelease
openConnection
closeConnection
return! runApplicationWork connection
})
lifecycle: ResizeArray<string>``.ctor``: unit -> unitInitializes a new instance of the class that is empty and has the default initial capacity.
stringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage1_7555C511A9CE.Connectionname: string_: ConnectionName: Connection -> unit -> stringopenConnection: Flow<string,Connection>FlowA flow that requires no environment and can fail with a typed error.
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. A flow that lazily evaluates the factory when executed. let flow = Flow.delay (fun () -> Flow.succeed 42)
Add: string -> 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.
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.
``.ctor``: string -> ConnectioncloseConnection: Connection -> CancellationToken -> Taskconnection: ConnectionSystem.Threading.CancellationTokenPropagates notification that operations should be canceled.
System.Threading.Tasks.TaskRepresents an asynchronous operation.
CompletedTask: TaskGets a task that has already completed successfully. The successfully completed task.
runApplicationWork: Connection -> Flow<string,string>application: Flow<string,string>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.
flow: FlowBuilderThe universal flow { } computation expression.
scopeAcquireRelease: Flow<'env,'error,'resource> -> ('resource -> CancellationToken -> Task) -> Flow<'env,'error,'resource>Acquires a value and registers its release with the current runtime scope. The flow that acquires the value. The release action run when the current scope closes. A flow that succeeds with the acquired value.
lifecycle.Clear()
application |> Flow.run () |> shouldEqual (Exit.Success "used orders")
List.ofSeq lifecycle |> shouldEqual [ "open"; "work"; "close orders" ]
lifecycle: ResizeArray<string>Clear: unit -> unitRemoves all elements from the .
application: Flow<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
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
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.
Everything registered inside Flow.scoped remains available across calls made inside that block. Cleanup finishes
before the resulting Flow returns.
Register with the current scope
The scope prefix means “attach this value or cleanup action to the current Flow scope”:
let registerEverything (stream: IDisposable) (response: IAsyncDisposable) : Flow<string, unit> =
flow {
do! Flow.scopeDisposable stream
do! Flow.scopeAsyncDisposable response
do! Flow.scopeFinalizer (fun _ -> lifecycle.Add "flush telemetry"; Task.CompletedTask)
do! Flow.scopeAsyncFinalizer (fun _ -> async { lifecycle.Add "save state" })
}
registerEverything: IDisposable -> IAsyncDisposable -> Flow<string,unit>stream: IDisposableSystem.IDisposableProvides a mechanism for releasing unmanaged resources.
response: IAsyncDisposableSystem.IAsyncDisposableProvides a mechanism for releasing unmanaged resources asynchronously.
FlowA flow that requires no environment and can fail with a typed error.
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.
Axial.FlowscopeDisposable: IDisposable -> Flow<'env,'error,unit>Registers a disposable resource with the current runtime scope. The disposable resource to close when the current scope closes. A flow that registers the resource.
scopeAsyncDisposable: IAsyncDisposable -> Flow<'env,'error,unit>Registers an asynchronously disposable resource with the current runtime scope. The async disposable resource to close when the current scope closes. A flow that registers the resource.
scopeFinalizer: (CancellationToken -> Task) -> Flow<'env,'error,unit>Registers an asynchronous finalizer with the current runtime scope. The finalizer to run when the current scope closes. A flow that registers the finalizer. Use this when a resource acquired by a subflow should live until the surrounding runtime or layer scope closes, rather than only until the current expression ends.
lifecycle: ResizeArray<string>Add: string -> 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.
System.Threading.Tasks.TaskRepresents an asynchronous operation.
CompletedTask: TaskGets a task that has already completed successfully. The successfully completed task.
scopeAsyncFinalizer: (CancellationToken -> Async<unit>) -> Flow<'env,'error,unit>Registers a F# async finalizer with the current runtime scope on .NET or Fable. Flow.scopeAsyncFinalizer (fun _ -> async { resource.Close() })
async: AsyncBuilderBuilds an asynchronous workflow using computation expression syntax. let sleepExample() = async { printfn "sleeping" do! Async.Sleep 10 printfn "waking up" return 6 } sleepExample() |> Async.RunSynchronously
Finalizers run in the reverse of their registration order when the scope closes:
lifecycle.Clear()
let stream = { new IDisposable with member _.Dispose() = lifecycle.Add "dispose stream" }
let response = { new IAsyncDisposable with member _.DisposeAsync() = lifecycle.Add "dispose response"; ValueTask() }
registerEverything stream response |> Flow.scoped |> Flow.run () |> shouldEqual (Exit.Success())
List.ofSeq lifecycle |> shouldEqual [ "save state"; "flush telemetry"; "dispose response"; "dispose stream" ]
lifecycle: ResizeArray<string>Clear: unit -> unitRemoves all elements from the .
stream: IDisposableSystem.IDisposableProvides a mechanism for releasing unmanaged resources.
_: IDisposableDispose: unit -> unitPerforms application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
Add: string -> 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.
response: IAsyncDisposableSystem.IAsyncDisposableProvides a mechanism for releasing unmanaged resources asynchronously.
_: IAsyncDisposableDisposeAsync: unit -> ValueTaskPerforms application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. A task that represents the asynchronous dispose operation.
``.ctor``: unitregisterEverything: IDisposable -> IAsyncDisposable -> Flow<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.Flowscoped: 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.
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.
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.
Use Flow.scopeDisposable and Flow.scopeAsyncDisposable for resources that already exist. Use
Flow.scopeFinalizer or Flow.scopeAsyncFinalizer for custom cleanup.
Flow.scopeAcquireRelease combines acquisition and registration without an interruption point between them:
type RequestCache() =
member _.Entries = Collections.Generic.Dictionary<string, string>()
interface IDisposable with
member _.Dispose() = lifecycle.Add "cache disposed"
let acquireRequestCache : Flow<string, RequestCache> =
Flow.scopeAcquireRelease
(Flow.succeed (new RequestCache()))
(fun cache _ ->
(cache :> IDisposable).Dispose()
Task.CompletedTask)
FsLiveDocsGeneratedPage1_7555C511A9CE.RequestCache_: RequestCacheEntries: RequestCache -> unit -> Collections.Generic.Dictionary<string,string>Collections``.ctor``: unit -> unitInitializes a new instance of the class that is empty, has the default initial capacity, and uses the default equality comparer for the key type.
GenericstringAn abbreviation for the CLI type . Basic Types
System.IDisposableProvides a mechanism for releasing unmanaged resources.
Dispose: RequestCache -> unit -> unitlifecycle: ResizeArray<string>Add: string -> 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.
acquireRequestCache: Flow<string,RequestCache>FlowA flow that requires no environment and can fail with a typed error.
Axial.FlowscopeAcquireRelease: Flow<'env,'error,'resource> -> ('resource -> CancellationToken -> Task) -> Flow<'env,'error,'resource>Acquires a value and registers its release with the current runtime scope. The flow that acquires the value. The release action run when the current scope closes. A flow that succeeds with the acquired value.
succeed: 'value -> Flow<'env,'error,'value>Same as ok. The value to wrap in a successful flow. A flow that always succeeds with the provided value. let result = Flow.succeed 42 |> Flow.run () // result = Success 42
cache: RequestCacheDispose: unit -> unitPerforms application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
System.Threading.Tasks.TaskRepresents an asynchronous operation.
CompletedTask: TaskGets a task that has already completed successfully. The successfully completed task.
The returned value remains available to later subflows in the same scope.
Reusable resource descriptions
Resource separates a reusable acquisition description from the scope that eventually owns it:
let connectionResource = Resource.create openConnection closeConnection
let query (connection: Connection) : Flow<string, int> = Flow.ok connection.Name.Length
let program : Flow<string, int> =
Flow.scoped (
flow {
let! connection =
connectionResource
|> Flow.scopeResource
return! query connection
})
connectionResource: Resource<unit,string,Connection>Axial.ResourceModulecreate: Flow<'env,'error,'value> -> ('value -> CancellationToken -> Task) -> Resource<'env,'error,'value>Describes acquisition together with a task-based release registered in the current Flow scope.
openConnection: Flow<string,Connection>closeConnection: Connection -> CancellationToken -> Taskquery: Connection -> Flow<string,int>connection: ConnectionFsLiveDocsGeneratedPage1_7555C511A9CE.ConnectionFlowA flow that requires no environment and can fail with a typed error.
stringAn abbreviation for the CLI type . Basic Types
intAn abbreviation for the CLI type . 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.
Length: intGets the number of characters in the current object. The number of characters in the current string.
Name: stringprogram: Flow<string,int>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.
flow: FlowBuilderThe universal flow { } computation expression.
(|>): '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
scopeResource: Resource<'env,'error,'value> -> Flow<'env,'error,'value>Acquires a described resource and registers its release with the current runtime scope. The acquisition and release description. A flow that succeeds with the acquired value.
lifecycle.Clear()
program |> Flow.run () |> shouldEqual (Exit.Success 6)
List.ofSeq lifecycle |> shouldEqual [ "open"; "close orders" ]
lifecycle: ResizeArray<string>Clear: unit -> unitRemoves all elements from the .
program: Flow<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
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.
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.
Use Resource.finalizer for task-based cleanup and Resource.asyncFinalizer for F# async cleanup.
Fibers and nested work
A fiber created with Flow.fork belongs to the current scope. Closing the scope interrupts and awaits an unfinished
fiber. A nested Flow.scoped therefore gives a group of resources and fibers a lifetime shorter than the surrounding
application without requiring every function to pass cleanup handles manually.
Child scopes are also owned by their parent. If the root execution is interrupted, it closes every remaining child in reverse registration order.
Streams
FlowStream terminal consumers create child scopes internally. Stream resources and parallel mapping fibers close on completion, failure, interruption, or early termination. The consuming streams guide explains that boundary from the stream user's perspective.
Rules to remember
- Use
useoruse!when one lexical body exclusively owns a disposable. - Use
Flow.scopedwhen ownership spans functions, subflows, fibers, or stream pulls. scope...functions attach cleanup to the current scope; they do not clean up immediately.- Ordinary
Flow.bindandflow { }nesting share the current scope. - Nested scopes close before their parent.
- Finalizers run in reverse registration order and at most once.
- Cleanup failures are defects and combine with the failure that caused closure.
Layers use the same model. Layer.acquireRelease keeps a provisioned service alive until Layer.provide finishes.

