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.JavaScriptThe Environment
There is no container and no registration step. 'env is an ordinary type parameter, and the value you supply is an
ordinary value.
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.
> (Flow.envWith (fun environment -> environment * 2) : Flow<int, Never, int>) |> Flow.run 21;;val it: Exit<int,Never> = Success 42The environment here is an int. Flow does not require a record, an interface, or a service. It hands your function
whatever value you passed to Flow.run.
What the functions do
Flow.envWith runs a function against the environment and continues with the result. _.Name is F# shorthand
for fun environment -> environment.Name, so Flow.envWith _.Name is the same thing written shorter:
> type Person = { Name: string; Age: int };;
> (Flow.envWith _.Name : Flow<Person, Never, string>) |> Flow.run { Name = "Ada"; Age = 36 };;val it: Exit<string,Never> = Success "Ada"The other environment functions:
| Function | What it does |
|---|---|
Flow.envWith projection |
Runs projection against the environment, continues with its result |
Flow.env |
Continues with the environment value itself |
Flow.localEnv change |
Runs a flow against a different environment computed by change |
Flow.localEnv is how a workflow needing a small environment runs inside one that has more:
> type Person = { Name: string; Age: int };;
> let nameLength () : Flow<string, Never, int> = Flow.envWith (fun name -> name.Length);;
> nameLength () |> Flow.localEnv (fun (person: Person) -> person.Name) |> Flow.run { Name = "Ada"; Age = 36 };;val it: Exit<int,Never> = Success 3What you will actually use
An int proves the point but is not the shape you want. In practice the environment is a record you define,
holding one field per dependency:
type User = { Id: int; Name: string }
type IUserStore =
abstract Load: int -> Result<User, string>
type IAuditLog =
abstract Record: string -> unit
type AppEnv =
{ Users: IUserStore
Audit: IAuditLog }
let loadUser (id: int) : Flow<AppEnv, string, User> =
flow {
let! users = Flow.envWith _.Users
let! audit = Flow.envWith _.Audit
let! user = users.Load id
audit.Record $"loaded {id}"
return user
}
FsLiveDocsGeneratedPage15_4556FC416C2B.UserId: intintAn abbreviation for the CLI type . Basic Types
Name: stringstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage15_4556FC416C2B.IUserStoreLoad: IUserStore -> int -> Result<User,string>Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
FsLiveDocsGeneratedPage15_4556FC416C2B.IAuditLogRecord: IAuditLog -> string -> unitunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
FsLiveDocsGeneratedPage15_4556FC416C2B.AppEnvUsers: IUserStoreAudit: IAuditLogloadUser: 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: AppEnvaudit: IAuditLog_arg3: AppEnvuser: UserLoad: int -> Result<User,string>Record: string -> unitYou construct that record in exactly two places:
- At boot, with the live implementations, for example
{ Users = SqlUserStore(connection); Audit = FileAuditLog(path) }. - In tests, with fakes: the same record type with different values.
type InMemoryUsers(users: User list) =
interface IUserStore with
member _.Load id =
match users |> List.tryFind (fun user -> user.Id = id) with
| Some user -> Ok user
| None -> Error $"no user {id}"
type ListAudit() =
let entries = ResizeArray<string>()
member _.Entries = List.ofSeq entries
interface IAuditLog with
member _.Record entry = entries.Add entry
FsLiveDocsGeneratedPage15_4556FC416C2B.InMemoryUsersusers: User listFsLiveDocsGeneratedPage15_4556FC416C2B.UserlistThe 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.
FsLiveDocsGeneratedPage15_4556FC416C2B.IUserStore_: InMemoryUsersLoad: InMemoryUsers -> int -> Result<User,string>id: 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
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.
tryFind: ('T -> bool) -> 'T list -> 'T optionReturns the first element for which the given function returns True. Return None if no such element exists. The function to test the input elements. The input list. The first element for which the predicate returns true, or None if every element evaluates to false. Try to find the first even number: let inputs = [1; 2; 3] inputs |> List.tryFind (fun elm -> elm % 2 = 0) Evaluates to Some 2 Try to find the first even number: let inputs = [1; 5; 3] inputs |> List.tryFind (fun elm -> elm % 2 = 0) Evaluates to None This is an O(n) operation in the worst case, where n is the length of the list.
user: UserId: 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
SomeThe representation of "Value of type 'T" The input value. An option representing the value.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
NoneThe representation of "No value"
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
FsLiveDocsGeneratedPage15_4556FC416C2B.ListAuditentries: 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
_: ListAuditEntries: ListAudit -> unit -> string listofSeq: '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.
FsLiveDocsGeneratedPage15_4556FC416C2B.IAuditLogRecord: ListAudit -> string -> unitentry: stringAdd: 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.
let audit = ListAudit()
let underTest = { Users = InMemoryUsers [ { Id = 42; Name = "Ada" } ]; Audit = audit }
loadUser 42 |> Flow.run underTest |> shouldEqual (Exit.Success { Id = 42; Name = "Ada" })
loadUser 7 |> Flow.run underTest |> shouldEqual (Exit.Failure(Cause.Fail "no user 7"))
audit.Entries |> shouldEqual [ "loaded 42" ]
audit: ListAudit``.ctor``: unit -> ListAuditunderTest: AppEnvUsers: IUserStore``.ctor``: User list -> InMemoryUsersId: intName: stringAudit: IAuditLogloadUser: 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
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.
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.
Entries: string listLarger systems often define one record per architectural boundary rather than a single application-wide one, and use
Flow.localEnv to move between them. A billing subsystem that cannot see the mailer is a record without a mailer
field, and that is enforced by the compiler rather than by convention.
Where the rest of the section goes
The rest of this section covers the cases a plain record does not:
- Choosing an approach: when arguments beat a record, and when a record stops being enough.
- Service contracts: how a package asks for a dependency without knowing your record type.
- Providing the environment: building the value at a host boundary.

