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:
let readFirstLineLexically path =
flow {
use reader = File.OpenText path
return! ColdTask(fun _ -> reader.ReadLineAsync())
}
readFirstLineLexically: 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.
Use a Flow scope when ownership must span functions, subflows, or fibers:
open System.IO
open System.Threading.Tasks
open Axial
let readFirstLine path =
Flow.scoped (
Flow.scopeAcquireRelease
(Flow.succeed (File.OpenText path))
(fun reader _ ->
reader.Dispose()
Task.CompletedTask)
|> Flow.bind (fun reader ->
flow {
return! ColdTask(fun _ -> reader.ReadLineAsync())
}))
SystemIOThreadingTasksAxialreadFirstLine: 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 -> 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
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: StreamReaderDispose: unit -> unitReleases all resources used by the object.
System.Threading.Tasks.TaskRepresents an asynchronous operation.
CompletedTask: TaskGets a task that has already completed successfully. The successfully completed task.
(|>): '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. 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.
Flow.scopeAcquireRelease acquires the value and registers its release with the current scope. Flow.scoped creates
the local ownership boundary and closes it after success, typed failure, defect, or interruption.
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 flow's own outcome is kept. The handler cannot fail with a typed error, and a defect it raises is added to the outcome.
> 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"]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.

