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.JavaScriptReading the Type
The full Flow type has three parameters:
Flow<'env, 'error, 'value>Read them from left to right:
| Parameter | Meaning |
|---|---|
'env |
Dependencies supplied when the workflow runs |
'error |
Expected failures the caller can handle |
'value |
The value produced on success |
For example:
let loadUser (id: UserId) : Flow<AppEnv, LoadUserError, User> = ...The type does not mean that work has started. A Flow is an immutable description, and building one starts nothing: each
execution interprets the description with an environment and produces an outcome. A Flow built from cold sources (the
usual case) starts fresh work on every execution. But a Flow can capture work that is already running —
Flow.awaitStartedTask observes a task that began before the Flow was built — so "cold" describes when the description
starts work, not the lifecycle of everything it holds.
Aliases
Short aliases are abbreviations for the same three-parameter type. Each one fixes the channels it leaves out:
| Alias | Expands to | Meaning |
|---|---|---|
Flow<'value> |
Flow<unit, Never, 'value> |
No environment and no typed failure |
Flow<'error, 'value> |
Flow<unit, 'error, 'value> |
Typed failure, no environment |
EnvFlow<'env, 'value> |
Flow<'env, Never, 'value> |
Environment, no typed failure |
ExnFlow<'value> |
Flow<unit, exn, 'value> |
Recoverable exceptions as typed failures |
ExnEnvFlow<'env, 'value> |
Flow<'env, exn, 'value> |
Environment and recoverable exceptions |
unit in the environment channel means the workflow reads nothing. Never is an error type with no values, so a
Flow<'value> or EnvFlow<'env, 'value> cannot fail with an expected error. Writing the alias and writing the
expansion produce the same type, so the two forms are interchangeable in a signature:
open Axial
Axiallet render : Flow<string> = Flow.succeed "ok"
let renderExpanded : Flow<unit, Never, string> = render
render: Flow<string>FlowA flow that requires no environment and cannot fail with a typed error.
stringAn abbreviation for the CLI type . Basic Types
Axial.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
renderExpanded: Flow<unit,Never,string>Axial.Flow`3unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
Axial.NeverRepresents an error channel that cannot occur.
Use an alias when it shortens a signature without hiding information.
What runs underneath
Running a Flow produces an awaitable outcome. On .NET that is a ValueTask, so work that completes synchronously
avoids a heap allocation. On Fable's JavaScript target it is an Async, because JavaScript has no value-type task.
Your code does not change between the two: Flow<'env, 'error, 'value> is the same type on both.
On .NET, Flow.bind and Flow.delay hand tail continuations back to an execution loop. A recursive workflow such
as let rec loop () = flow { do! step; return! loop () } does not retain a pending task for every iteration.
Pending work after a recursive call still needs storage: an enclosing try/finally, for example, must keep its
cleanup until the recursive call finishes. Release per-iteration resources before the next recursive call, or use a
while loop whose body owns those resources.
See Async and Task interop for binding existing Async, Task, and
ValueTask code.
Related guides
- Flow API reference maps the construction, environment, composition, execution, resource, and concurrency functions.
- Troubleshooting Types explains the compiler errors produced when environment or error channels do not line up.

