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.JavaScriptTroubleshooting Types
These compiler errors often indicate a mismatch between wrapper types.
Error: A Flow Alias Does Not Match The Channels You Intended
Flow<'env, 'error, 'value> is the full workflow shape. The shorter aliases remove common channels; the complete
alias table, with what each expansion means, is in Reading the type.
If your workflow reads an environment and has a typed domain error, use the full Flow<'env, 'error, 'value> form.
FS0030: Value Restriction On A Generic Flow
A value cannot be generic over an environment type. This declaration fails with FS0030 (the value restriction):
type Settings = { Name: string }
let load : Flow<'env, Never, Settings> when 'env :> IHasFileSystem =
Flow.ok { Name = "default" }Make load a type function instead:
Show page setup
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.FileSystem
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actualtype Settings = { Name: string }
let loadSettings<'env when 'env :> IHasFileSystem> : Flow<'env, Never, Settings> =
Flow.ok { Name = "default" }
FsLiveDocsGeneratedPage11_2D306DEB1823.SettingsName: stringstringAn abbreviation for the CLI type . Basic Types
loadSettings: Flow<'env,Never,Settings>envAxial.FileSystem.IHasFileSystemAxial.Flow`3Axial.NeverRepresents an error channel that cannot occur.
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.
Requires & types
Requires: open Axial; open Axial.FileSystem (packages: Axial, Axial.FileSystem)val loadSettings: Flow<'env,Never,Settings>let env = { new IHasFileSystem with member _.FileSystem = TestFileSystem() :> IFileSystem }
loadSettings<IHasFileSystem> |> Flow.run env |> shouldEqual (Exit.Success { Name = "default" })
env: IHasFileSystemAxial.FileSystem.IHasFileSystem_: IHasFileSystemFileSystem: unit -> IFileSystemThe file-system service supplied by this environment.
``.ctor``: (string * string) seq option * (string -> Flow<unit,FileSystemError,string>) option -> TestFileSystemAxial.FileSystem.IFileSystemProvides access to common file, directory, and path operations.
loadSettings: Flow<'env,Never,Settings>(|>): '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.
Name: stringRequires & types
Requires: open Axial; open Axial.FileSystem (packages: Axial, Axial.FileSystem)val env: IHasFileSystemError: A Unique Overload For Method Bind Could Not Be Determined
The compiler cannot determine which wrapper shape a let! value should use.
Example:
let nested : Async<Async<Result<int, string>>> =
async {
return async { return Ok 42 }
}
let workflow : Flow<unit, string, int> =
flow {
let! next = nested
let! value = next
return value
}
nested: Async<Async<Result<int,string>>>Microsoft.FSharp.Control.FSharpAsync`1An asynchronous computation, which, when run, will eventually produce a value of type T, or else raises an exception. This type has no members. Asynchronous computations are normally specified either by using an async expression or the static methods in the type. See also F# Language Guide - Async Workflows. Library functionality for asynchronous programming, events and agents. See also Asynchronous Programming, Events and Lazy Expressions in the F# Language Guide. Async Programming
Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
intAn abbreviation for the CLI type . Basic Types
stringAn abbreviation for the CLI type . Basic Types
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
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
workflow: Flow<unit,string,int>Axial.Flow`3unitThe 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.
next: Async<Result<int,string>>value: intRequires & types
Requires: open Axial (packages: Axial)val nested: Async<Async<Result<int,string>>>val workflow: Flow<unit,string,int>The second let! is ambiguous.
Fix it with a type annotation:
let annotated : Flow<unit, string, int> =
flow {
let! next = nested
let! (value: int) = next
return value
}
annotated: Flow<unit,string,int>Axial.Flow`3unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
stringAn abbreviation for the CLI type . Basic Types
intAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
next: Async<Result<int,string>>nested: Async<Async<Result<int,string>>>value: intRequires & types
Requires: open Axial (packages: Axial)val annotated: Flow<unit,string,int>annotated |> Flow.run () |> shouldEqual (Exit.Success 42)
annotated: Flow<unit,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.
Requires & types
Requires: open Axial (packages: Axial)Error: The Flow Requires A Different Environment Type
A workflow written against one environment type (or service contract) cannot run directly inside a different environment type.
Example 1: Records
type SmallEnv = { Prefix: string }
type BigEnv = { App: SmallEnv; RequestId: string }
let greet : Flow<SmallEnv, string, string> =
flow {
let! prefix = Flow.envWith _.Prefix
return $"{prefix} world"
}
// Run in BigEnv using localEnv
let greetInBigEnv : Flow<BigEnv, string, string> =
greet |> Flow.localEnv _.App
FsLiveDocsGeneratedPage11_2D306DEB1823.SmallEnvPrefix: stringstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage11_2D306DEB1823.BigEnvApp: SmallEnvRequestId: stringgreet: Flow<SmallEnv,string,string>Axial.Flow`3flow: FlowBuilderThe universal flow { } computation expression.
prefix: stringAxial.FlowenvWith: ('env -> 'value) -> Flow<'env,'error,'value>Projects one value from the current environment. This is the primary way to access app dependencies, configuration, or request metadata stored in env. The projection runs only when the flow is executed, so constructing the flow is still pure and side-effect free. Prefer small projections over passing a large environment deeper into reusable helpers. A function that extracts a value from the environment. A containing the projected value. let currentTime () = Flow.envWith (fun (environment: BaseRuntime) -> environment.Clock.UtcNow())
_arg1: SmallEnvgreetInBigEnv: Flow<BigEnv,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
localEnv: ('outerEnvironment -> 'innerEnvironment) -> Flow<'innerEnvironment,'error,'value> -> Flow<'outerEnvironment,'error,'value>Runs a flow against an environment derived from the outer environment. Use this to embed a smaller workflow inside a larger application environment without changing the smaller workflow's type. The mapping is applied at execution time. This is useful for preserving narrow helper signatures while still running everything from one app boundary. A function that maps the outer environment to the inner environment. The flow to run with the inner environment. A flow that expects the outer environment. let flow = Flow.succeed 1 |> Flow.localEnv (fun outer -> outer)
_arg1: BigEnvRequires & types
Requires: open Axial (packages: Axial)val greet: Flow<SmallEnv,string,string>val greetInBigEnv: Flow<BigEnv,string,string>greetInBigEnv
|> Flow.run { App = { Prefix = "hello" }; RequestId = "r-1" }
|> shouldEqual (Exit.Success "hello world")
greetInBigEnv: Flow<BigEnv,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
App: SmallEnvPrefix: stringRequestId: stringshouldEqual: '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 (packages: Axial)Example 2: Services
If a helper requires IHasDatabase but you are running it in an environment that doesn't implement it, the compiler will error.
let helper : Flow<#IHasDatabase, _, _> = ...
// This fails if AppEnv doesn't implement IHasDatabase
let run (env: AppEnv) = helper |> Flow.startTask envFix it by implementing the interface on your environment type.
Error: Option Or ValueOption Does Not Match Your Error Type
Implicit option binding only works when the workflow error type is unit.
This fails:
let workflow : Flow<unit, string, int> =
flow {
let! value = Some 42
return value
}Use an explicit adapter when you want a custom error:
let optionWorkflow : Flow<unit, string, int> =
Some 42
|> Flow.fromOption "missing value"
optionWorkflow: Flow<unit,string,int>Axial.Flow`3unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
stringAn abbreviation for the CLI type . Basic Types
intAn abbreviation for the CLI type . Basic Types
SomeThe representation of "Value of type 'T" The input value. An option representing the 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
Axial.FlowfromOption: 'error -> 'value option -> Flow<'env,'error,'value>Lifts an option into a synchronous flow with the supplied error. The error to return if the option is None. The option to lift. A flow that succeeds with the option's value or fails with the provided error. let opt = Some "value" Flow.fromOption "missing" opt |> Flow.run ()
Requires & types
Requires: open Axial (packages: Axial)val optionWorkflow: Flow<unit,string,int>optionWorkflow |> Flow.run () |> shouldEqual (Exit.Success 42)
(None |> Flow.fromOption "missing value" : Flow<unit, string, int>) |> Flow.run () |> shouldEqual (Exit.Failure(Cause.Fail "missing value"))
optionWorkflow: Flow<unit,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.
NoneThe representation of "No value"
fromOption: 'error -> 'value option -> Flow<'env,'error,'value>Lifts an option into a synchronous flow with the supplied error. The error to return if the option is None. The option to lift. A flow that succeeds with the option's value or fails with the provided error. let opt = Some "value" Flow.fromOption "missing" opt |> Flow.run ()
Axial.Flow`3unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
stringAn abbreviation for the CLI type . Basic Types
intAn abbreviation for the CLI type . Basic Types
FailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
Requires & types
Requires: open Axial (packages: Axial)Error: Task is not a Flow builder source
Raw Task<'value> and ValueTask<'value> values do not bind directly in flow { }. A task is already running, while
Flow is a cold description that may run more than once.
Wrap work that should start when the Flow runs in ColdTask:
let loadAsync (cancellationToken: CancellationToken) : Task<int> =
task {
do! Task.Delay(1, cancellationToken)
return 42
}
let load : ColdTask<int> = ColdTask loadAsync
let coldWorkflow : Flow<int> =
flow {
let! value = load
return value
}
loadAsync: CancellationToken -> Task<int>cancellationToken: CancellationTokenSystem.Threading.CancellationTokenPropagates notification that operations should be canceled.
System.Threading.Tasks.Task`1Represents an asynchronous operation that can return a value. The type of the result produced by this .
intAn abbreviation for the CLI type . Basic Types
task: TaskBuilderBuilds a task using computation expression syntax.
System.Threading.Tasks.TaskRepresents an asynchronous operation.
Delay: int * CancellationToken -> TaskCreates a cancellable task that completes after a specified number of milliseconds. The number of milliseconds to wait before completing the returned task, or -1 to wait indefinitely. A cancellation token to observe while waiting for the task to complete. A task that represents the time delay. The argument is less than -1. The task has been canceled. The provided has already been disposed.
load: ColdTask<int>Axial.ColdTask`1Represents delayed task work that can observe a runtime cancellation token when it is started. Bind a cold task directly in flow { }. When the task produces Result<'value,'error>, the builder places Error in Flow's typed error channel for both let! and return!. A raw, already-started Task is not a Flow builder source. The type of the produced task value.
ColdTaskcoldWorkflow: Flow<int>FlowA flow that requires no environment and cannot fail with a typed error.
flow: FlowBuilderThe universal flow { } computation expression.
value: intRequires & types
Requires: open Axial; open System.Threading; open System.Threading.Tasks (packages: Axial)val loadAsync: CancellationToken -> Task<int>val load: ColdTask<int>val coldWorkflow: Flow<int>coldWorkflow |> Flow.run () |> shouldEqual (Exit.Success 42)
coldWorkflow: Flow<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.
Requires & types
Requires: open Axial (packages: Axial)If the cold task returns Result<'value,'error>, let! and return! place Error in Flow's typed error channel.
When work has already started, name that lifecycle explicitly:
let runningTask : Task<int> = loadAsync CancellationToken.None
let startedWorkflow : Flow<int> =
flow {
let! value = Flow.awaitStartedTask runningTask
return value
}
runningTask: Task<int>System.Threading.Tasks.Task`1Represents an asynchronous operation that can return a value. The type of the result produced by this .
intAn abbreviation for the CLI type . Basic Types
loadAsync: CancellationToken -> Task<int>System.Threading.CancellationTokenPropagates notification that operations should be canceled.
None: CancellationTokenReturns an empty value. An empty cancellation token.
startedWorkflow: Flow<int>FlowA flow that requires no environment and cannot fail with a typed error.
flow: FlowBuilderThe universal flow { } computation expression.
value: intAxial.FlowawaitStartedTask: Task<'value> -> Flow<'env,'error,'value>Observes a task that has already been started. The operation is in flight before this is called. It therefore starts outside the workflow, ignores the runtime's cancellation token, and yields the same single result no matter how many times the flow is executed. Prefer fromTask, which keeps the flow cold. Thrown exceptions are recorded as defects (Cause.Die). A task that is already running. .NET only
Requires & types
Requires: open Axial; open System.Threading; open System.Threading.Tasks (packages: Axial)val runningTask: Task<int>val startedWorkflow: Flow<int>startedWorkflow |> Flow.run () |> shouldEqual (Exit.Success 42)
startedWorkflow: Flow<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.
Requires & types
Requires: open Axial (packages: Axial)Use Flow.awaitStartedTaskResult when the started task returns Result.
Flexible type variables are each distinct
#IHasOrders is F# shorthand for 'env when 'env :> IHasOrders, a fresh type variable with one constraint. Each
occurrence of #T in a signature is a separate type variable, so two occurrences do not unify to the same
environment unless you name the variable explicitly.
type ICheckoutEnv =
inherit IHasOrders
inherit IHasEmail
let submitOrder (order: Order) : Flow<#ICheckoutEnv, CheckoutError, unit> = submit orderWhen the same environment appears more than once — two inputs, or an input and an output — write the explicit form so all occurrences name one variable:
type IHasOrders =
abstract Orders: unit
let sequence<'env when 'env :> IHasOrders> (a: Flow<'env, 'e, 'v>) (b: Flow<'env, 'e, 'v>) : Flow<'env, 'e, 'v> =
Flow.bind (fun _ -> b) a
FsLiveDocsGeneratedPage11_2D306DEB1823.IHasOrdersOrders: IHasOrders -> unit -> unitunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
sequence: Flow<'env,obj,obj> -> Flow<'env,obj,obj> -> Flow<'env,obj,obj>enva: Flow<'env,obj,obj>Axial.Flow`3evb: Flow<'env,obj,obj>Axial.Flowbind: ('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))
Requires & types
Requires: open Axial (packages: Axial)val sequence: Flow<'env,obj,obj> -> Flow<'env,obj,obj> -> Flow<'env,obj,obj>See service contracts for when to declare one.
Check wrapper boundaries
If the compiler error mentions one of these shapes, check the boundary first:
Result<...>Async<...>Async<Result<...>>Task<...>Task<Result<...>>Flow<...>
Flow.retry and Flow.repeat take a Schedule. If the compiler reports a mismatch on the schedule's input type, check that the schedule's input matches the flow's error type (for retry) or value type (for repeat).
To resolve the mismatch:
- add a type annotation to disambiguate
let!overloads - derive a smaller local environment with
localEnv - use
Bind.errororBind.mapErrorat aflow { }bind site when the source error must be assigned or mapped first - move back to plain Result until the real workflow boundary appears

