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.JavaScript

The Environment

There is no container and no registration step. 'env is an ordinary type parameter, and the value you supply is an ordinary value.

Verified
> (Flow.envWith (fun environment -> environment * 2) : Flow<int, Never, int>) |> Flow.run 21;;
val it: Exit<int,Never> = Success 42

The 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:

Verified
> 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:

Verified
> 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 3

Application records

An application environment is usually a record with one field per dependency, and a workflow reads its fields with Flow.envWith:

Show page 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.
Verified
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
    }

Construct that record at boot with the live implementations and in tests with fakes: the same record type, different values. The App Record tutorial builds a complete application record, shows the boot and test construction, and proves a moved dependency still receives the right value. Larger 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, enforced by the compiler rather than by convention.