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.JavaScriptResources
Use use or use! when one flow { } body owns an IDisposable for its whole lifetime:
open System.IO
open Axial
let readFirstLineLexically path =
flow {
use reader = File.OpenText path
return! ColdTask(fun _ -> reader.ReadLineAsync())
}
SystemIOAxialreadFirstLineLexically: 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.
Requires & types
Requires: open Axial; open System.IO (packages: Axial)val readFirstLineLexically: string -> Flow<'a,'b,string>use disposes a value that already exists. use! first runs a Flow to obtain the disposable, then owns it for the
rest of the body:
let readFirstLineWithUseBang path =
flow {
use! reader = Flow.succeed (File.OpenText path)
return! ColdTask(fun _ -> reader.ReadLineAsync())
}
readFirstLineWithUseBang: string -> Flow<'a,'b,string>path: stringflow: FlowBuilderThe universal flow { } computation expression.
reader: StreamReaderAxial.Flowsucceed: '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
System.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.
Requires & types
Requires: open Axial; open System.IO (packages: Axial)val readFirstLineWithUseBang: string -> Flow<'a,'b,string>Both are lexical: the compiler disposes the resource when control leaves the body governed by the binding, even
when return! fails or the flow is interrupted. Prefer them when one flow { } body owns the resource for its whole
lifetime; use a Flow scope when ownership must span functions, subflows, or fibers.
open System.IO
open Axial
let readFirstLine path =
Flow.scoped (
Flow.scopeAcquireRelease
(Flow.succeed (File.OpenText path))
(fun reader ->
Flow.delay (fun () ->
reader.Dispose()
Flow.succeed ()))
|> Flow.bind (fun reader ->
flow {
return! ColdTask(fun _ -> reader.ReadLineAsync())
}))
SystemIOAxialreadFirstLine: string -> Flow<'a,'b,string>path: stringAxial.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.
scopeAcquireRelease: Flow<'env,'error,'resource> -> ('resource -> Flow<'env,Never,unit>) -> Flow<'env,'error,'resource>Acquires a value and registers its release with the current runtime scope. Same as Resource.create acquire release |> Flow.scopeResource; see Resource.create for how the release runs. The flow that acquires the value. The release flow 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
System.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.
reader: StreamReaderdelay: (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)
Dispose: unit -> unitReleases all resources used by the object.
(|>): '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
bind: ('value -> Flow<'env,'error,'next>) -> Flow<'env,'error,'value> -> Flow<'env,'error,'next>Sequences a dependent flow after a successful value. This is the flatmap operation for . The continuation only runs when the source flow succeeds, and it receives the successful value. Use bind when the next effect depends on the previous result; use map when the next step is pure. On .NET, a recursive return! through bind does not keep a task per call alive. Work still pending in the source, such as a use or try/finally, lives until the source completes. A function that takes the successful value and returns a new flow. The source flow to sequence. A representing the combined workflow. let flow = Flow.succeed 1 |> Flow.bind (fun x -> Flow.succeed (x + 1))
flow: FlowBuilderThe universal flow { } computation expression.
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.
Requires & types
Requires: open Axial; open System.IO (packages: Axial)val readFirstLine: string -> Flow<'a,'b,string>Flow.scopeAcquireRelease acquires the value and registers its release with the current scope. The release is a flow
that cannot fail with a typed error; it runs without interruption, with the environment captured at acquisition.
Flow.scoped creates the local ownership boundary and closes it after success, typed failure, defect, or
interruption.
Both examples open the file with File.OpenText to keep the focus on ownership and cleanup. That is an ambient
.NET call, not the IFileSystem service: it is not interruptible, and I/O failures are not modeled as a typed
FileSystemError. For the recommended pattern — an explicit filesystem dependency with a typed error channel — see
FileSystem.
For reusable acquisition descriptions and direct registration of disposables or finalizers, see scopes and resources.
Cleanup for one expression
When cleanup belongs to one flow rather than to a resource, attach it to the flow itself:
Flow.ensuringfinalizerrunsfinalizerafter the flow, however it ends.Flow.onExithandlerpasses the flow'sExittohandler, for cleanup that depends on how the flow ended.Flow.onInterrupthandlerrunshandleronly when the flow is interrupted.
The handler runs without the flow's cancellation, so the interruption that ended the flow cannot cut the cleanup short.
The handler cannot fail with a typed error. If the flow succeeded, a defect in the handler replaces the success with
that defect; if the flow failed, the defect is appended to the original cause as Cause.Then, keeping the flow's own
cause first.
> open Axial.PlatformService;;
> (flow {
- let log = ResizeArray<string>()
- let note text : Flow<ClockEnvironment, Never, unit> = Flow.delay (fun () -> log.Add text; Flow.ok ())
- let! worker =
- Flow.never<ClockEnvironment, Never, unit>
- |> Flow.onInterrupt (note "interrupted")
- |> Flow.ensuring (note "closed")
- |> Flow.fork
- let! _ = Fiber.interrupt worker
- let! _ = Flow.ok 1 |> Flow.onInterrupt (note "not logged") |> Flow.ensuring (note "closed")
- return List.ofSeq log
- } : Flow<ClockEnvironment, Never, string list>)
- |> Flow.run (ClockEnvironment Clock.live);;val it: Exit<string list,Never> = Success ["interrupted"; "closed"; "closed"]AxialPlatformServiceflow: FlowBuilderThe universal flow { } computation expression.
log: 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
note: string -> Flow<ClockEnvironment,Never,unit>text: stringAxial.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.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)
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.
worker: Fiber<Never,unit>never: Flow<'env,'error,'value>A flow that never completes on its own; it ends only when it is interrupted. Use it to keep a flow running until its fiber or scope is interrupted, such as a service's main loop that only waits for shutdown. It holds no thread while it waits. // Runs the worker until the service is interrupted, then lets it drain its queue. let serve (jobs: Queue<string>) (worker: Flow<unit, Never, unit>) : Flow<unit, Never, unit> = flow { let! _ = worker |> Flow.forkGraceful (Dequeue.shutdown jobs) (TimeSpan.FromSeconds 5.0) return! Flow.never }
(|>): '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
onInterrupt: Flow<'env,Never,unit> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Runs a handler only if the flow is interrupted. The handler runs when the flow's outcome includes an interruption, before the interruption propagates, and without the flow's cancellation, so it always finishes. Success, typed failure, and defects skip it. The cleanup to run on interruption; it cannot fail with a typed error. The source flow. upload |> Flow.onInterrupt (deletePartialFile path)
ensuring: Flow<'env,Never,unit> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Runs a finalizer after the flow, however it ends. The finalizer runs after success, typed failure, defect, and interruption alike, without the flow's cancellation, so it always finishes. The flow's outcome is kept; a defect in the finalizer is added to it. The cleanup to run; it cannot fail with a typed error. The source flow. render frame |> Flow.ensuring (Flow.delay (fun () -> canvas.Unlock(); Flow.ok ()))
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.
interrupt: Fiber<'error,'value> -> Flow<'env,'none,Exit<'value,'error>>Signals a fiber to stop, waits for its cleanup, and returns its final exit. Interruption requests cooperative cancellation through the fiber's cancellation source. A fiber that had already settled keeps its original exit. The fiber to interrupt. A flow that completes with the fiber's final outcome after interruption.
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.
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 first worker never finished on its own, so interrupting it ran both handlers in order. The second flow succeeded,
so only its ensuring finalizer ran. Flow.never is a flow that only ends when it is interrupted, such as a service
that waits for shutdown.

