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.JavaScriptService Contracts
A record works because you own both sides: the workflow names AppEnv, and you supply an AppEnv. A package
author cannot do that. Axial.Console is compiled long before your AppEnv exists, so Console.writeLine cannot
mention it.
A contract is how the package asks anyway. It is an ordinary interface named IHasFoo, with a single
member Foo:
The examples on this page use these application types:
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.
Shared setup
type Order = { Id: int }
type CheckoutError = AlreadySaved of int
type IOrderRepository =
abstract Save: Order -> Result<unit, CheckoutError>
type IEmailSender =
abstract SendConfirmation: Order -> unit
FsLiveDocsGeneratedPage17_4556FC416C2B.OrderId: intintAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage17_4556FC416C2B.CheckoutErrorAlreadySavedFsLiveDocsGeneratedPage17_4556FC416C2B.IOrderRepositorySave: IOrderRepository -> Order -> Result<unit,CheckoutError>Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
FsLiveDocsGeneratedPage17_4556FC416C2B.IEmailSenderSendConfirmation: IEmailSender -> Order -> unittype IHasOrders =
abstract Orders: IOrderRepository
FsLiveDocsGeneratedPage17_4556FC416C2B.IHasOrdersOrders: IHasOrders -> unit -> IOrderRepositoryFsLiveDocsGeneratedPage17_4556FC416C2B.IOrderRepositoryThe helper constrains its environment to that interface instead of naming a type:
[<RequireQualifiedAccess>]
module Orders =
let service<'env, 'error when 'env :> IHasOrders> : Flow<'env, 'error, IOrderRepository> =
Flow.envWith _.Orders
let save order : Flow<#IHasOrders, CheckoutError, unit> =
flow {
let! orders = Orders.service
do! orders.Save order
}
Microsoft.FSharp.Core.RequireQualifiedAccessAttributeThis attribute is used to indicate that references to the elements of a module, record or union type require explicit qualified access. Attributes
FsLiveDocsGeneratedPage17_4556FC416C2B.Ordersservice: Flow<'env,'error,IOrderRepository>enverrorFsLiveDocsGeneratedPage17_4556FC416C2B.IHasOrdersAxial.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.
FsLiveDocsGeneratedPage17_4556FC416C2B.IOrderRepositoryAxial.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: 'envOrders: IOrderRepositorysave: Order -> Flow<'a,CheckoutError,unit>order: OrderFsLiveDocsGeneratedPage17_4556FC416C2B.CheckoutErrorunitThe 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.
orders: IOrderRepositorySave: Order -> Result<unit,CheckoutError>Read Flow<#IHasOrders, …> as "any environment that can give me an IOrderRepository". This is still just
Flow.envWith; the interface only says which member it may read.
Supplying one
Your record implements the contracts it satisfies. One line each, and the fields keep whatever names you gave them:
type IHasEmail =
abstract Email: IEmailSender
[<RequireQualifiedAccess>]
module Email =
let service<'env, 'error when 'env :> IHasEmail> : Flow<'env, 'error, IEmailSender> =
Flow.envWith _.Email
type AppEnv =
{ Orders: IOrderRepository
Email: IEmailSender }
interface IHasOrders with member this.Orders = this.Orders
interface IHasEmail with member this.Email = this.Email
FsLiveDocsGeneratedPage17_4556FC416C2B.IHasEmailEmail: IHasEmail -> unit -> IEmailSenderFsLiveDocsGeneratedPage17_4556FC416C2B.IEmailSenderMicrosoft.FSharp.Core.RequireQualifiedAccessAttributeThis attribute is used to indicate that references to the elements of a module, record or union type require explicit qualified access. Attributes
FsLiveDocsGeneratedPage17_4556FC416C2B.Emailservice: Flow<'env,'error,IEmailSender>enverrorAxial.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.
Axial.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: 'envEmail: IEmailSenderFsLiveDocsGeneratedPage17_4556FC416C2B.AppEnvOrders: IOrderRepositoryFsLiveDocsGeneratedPage17_4556FC416C2B.IOrderRepositoryFsLiveDocsGeneratedPage17_4556FC416C2B.IHasOrdersthis: AppEnvOrders: AppEnv -> unit -> IOrderRepositoryEmail: AppEnv -> unit -> IEmailSendermember this.Orders = this.Orders is not recursive. F# interface implementations are always explicit, so the
interface member is not in scope on the record itself and the right-hand side resolves to the field. A record with no
such field fails to compile rather than looping.
Needing more than one
Contracts are distinct interfaces, so a workflow can require several and the constraints merge on their own:
let submit<'env when 'env :> IHasOrders and 'env :> IHasEmail> (order: Order) : Flow<'env, CheckoutError, unit> =
flow {
let! orders = Orders.service
let! email = Email.service
do! orders.Save order
email.SendConfirmation order
}
submit: Order -> Flow<'env,CheckoutError,unit>envFsLiveDocsGeneratedPage17_4556FC416C2B.IHasOrdersFsLiveDocsGeneratedPage17_4556FC416C2B.IHasEmailorder: OrderFsLiveDocsGeneratedPage17_4556FC416C2B.OrderAxial.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.
FsLiveDocsGeneratedPage17_4556FC416C2B.CheckoutErrorunitThe 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.
orders: IOrderRepositoryFsLiveDocsGeneratedPage17_4556FC416C2B.Ordersservice: Flow<'env,'error,IOrderRepository>email: IEmailSenderFsLiveDocsGeneratedPage17_4556FC416C2B.Emailservice: Flow<'env,'error,IEmailSender>Save: Order -> Result<unit,CheckoutError>SendConfirmation: Order -> unitAppEnv satisfies both constraints, so it runs save and submit alike:
let saved = ResizeArray<int>()
let sent = ResizeArray<int>()
let env =
{ Orders =
{ new IOrderRepository with
member _.Save order =
if saved.Contains order.Id then
Error(AlreadySaved order.Id)
else
saved.Add order.Id
Ok() }
Email = { new IEmailSender with member _.SendConfirmation order = sent.Add order.Id } }
submit { Id = 1 } |> Flow.run env |> shouldEqual (Exit.Success())
save { Id = 1 } |> Flow.run env |> shouldEqual (Exit.Failure(Cause.Fail(AlreadySaved 1)))
List.ofSeq sent |> shouldEqual [ 1 ]
saved: ResizeArray<int>``.ctor``: unit -> unitInitializes a new instance of the class that is empty and has the default initial capacity.
intAn abbreviation for the CLI type . Basic Types
sent: ResizeArray<int>env: AppEnvOrders: IOrderRepositoryFsLiveDocsGeneratedPage17_4556FC416C2B.IOrderRepository_: IOrderRepositorySave: Order -> Result<unit,CheckoutError>order: OrderContains: int -> boolDetermines whether an element is in the . The object to locate in the . The value can be for reference types. if is found in the ; otherwise, .
Id: intErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
AlreadySavedAdd: int -> 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.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
Email: IEmailSenderFsLiveDocsGeneratedPage17_4556FC416C2B.IEmailSender_: IEmailSenderSendConfirmation: Order -> unitsubmit: Order -> Flow<'env,CheckoutError,unit>(|>): '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.
save: Order -> Flow<'a,CheckoutError,unit>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.
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.
ofSeq: '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.
When a combination recurs, name it and a single constraint covers it:
type ICheckoutEnv =
inherit IHasOrders
inherit IHasEmail
let submitOrder (order: Order) : Flow<#ICheckoutEnv, CheckoutError, unit> = submit order
FsLiveDocsGeneratedPage17_4556FC416C2B.ICheckoutEnvFsLiveDocsGeneratedPage17_4556FC416C2B.IHasOrdersFsLiveDocsGeneratedPage17_4556FC416C2B.IHasEmailsubmitOrder: Order -> Flow<'a,CheckoutError,unit>order: OrderFsLiveDocsGeneratedPage17_4556FC416C2B.OrderAxial.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.
FsLiveDocsGeneratedPage17_4556FC416C2B.CheckoutErrorunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
submit: Order -> Flow<'env,CheckoutError,unit>#IHasOrders carries exactly one constraint, which is why the aggregate exists. Use the explicit
<'env when 'env :> … and 'env :> …> form when you need several, or when the environment appears more than once in a
signature, because each occurrence of #T is a separate type variable.
When to declare one
For application code, don't. A record is simpler, needs no interface, and is what the environment documents.
Declare a contract when you are publishing a helper whose callers you will never see, such as a shared library or a
package like Axial.FileSystem. A record cannot cover that case, because the helper cannot know the caller's record
type.
Writing a package that ships services is covered in providing services from a package.

