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.JavaScriptDependencies
Pass dependencies as ordinary function arguments until several workflows need the same ones and threading them through unrelated callers becomes noise. Then use an environment.
Then pass Flow a record. A workflow states what it needs in its environment channel; you build that record and hand it over when the workflow runs:
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.
type User = { Id: int; Name: string }
type IUserStore =
abstract Load: int -> Result<User, string>
type AppEnv = { Users: IUserStore }
let loadUser (id: int) : Flow<AppEnv, string, User> =
flow {
let! users = Flow.envWith _.Users
return! users.Load id
}
FsLiveDocsGeneratedPage21_4556FC416C2B.UserId: intintAn abbreviation for the CLI type . Basic Types
Name: stringstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage21_4556FC416C2B.IUserStoreLoad: IUserStore -> int -> Result<User,string>Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
FsLiveDocsGeneratedPage21_4556FC416C2B.AppEnvUsers: IUserStoreloadUser: int -> Flow<AppEnv,string,User>id: intAxial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
flow: FlowBuilderThe universal flow { } computation expression.
users: IUserStoreAxial.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: AppEnvLoad: int -> Result<User,string>let users =
{ new IUserStore with
member _.Load id = if id = 1 then Ok { Id = 1; Name = "Ada" } else Error $"no user {id}" }
loadUser 1 |> Flow.run { Users = users } |> shouldEqual (Exit.Success { Id = 1; Name = "Ada" })
users: IUserStoreFsLiveDocsGeneratedPage21_4556FC416C2B.IUserStore_: IUserStoreLoad: int -> Result<User,string>id: int(=): 'T -> 'T -> boolStructural equality The first parameter. The second parameter. The result of the comparison. 5 = 5 // Evaluates to true 5 = 6 // Evaluates to false [1; 2] = [1; 2] // Evaluates to true (1, 5) = (1, 6) // Evaluates to false
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
Id: intName: stringErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
loadUser: int -> Flow<AppEnv,string,User>(|>): '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
Users: IUserStoreshouldEqual: '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.
Most applications need nothing more. There is no container, registration, or resolution step: a test supplies a different record, with fakes in place of the live services.
Two further mechanisms build on it. Contracts let a package ask for a service without
knowing your record type; that is how Console.writeLine and the rest of the
built-in services work, and how you would publish your own. Layers are for provisioning
that is itself effectful; see layers, a separate package.
In this section
- The environment: what
'envactually is, and the functions that read it. - Choosing an approach: arguments, records, contracts, and layers compared.
- Service contracts: how a package asks for a dependency it cannot name.
- Providing the environment: building the value at a host boundary.
- Tutorials: the same material worked end to end.
For the services Axial already implements (the clock, console, file system, processes, and HTTP), see built-in services.

