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.JavaScriptBind
Use Bind.error and Bind.mapError to give different bind sources the same error-assignment syntax.
Why use Bind
Without Bind, you must adapt the right-hand side before flow { } can bind it. The required transformation depends
on that source's shape. For example, mapping an Async<Result<_,_>> error requires another async { } block:
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 AuthError = Denied of string
type TokenError = Expired of string
type SessionError =
| Unauthorized of AuthError
| TokenFailed of TokenError
| ProfileNotFound
let authorize (user: string) : Async<Result<unit, AuthError>> =
async { return if user = "ada" then Ok() else Error(Denied user) }
let createToken (user: string) : Result<string, TokenError> =
if user = "ada" then Ok $"token-{user}" else Error(Expired user)
let authorizeForLogin (user: string) : Async<Result<unit, SessionError>> =
async {
let! result = authorize user
return result |> Result.mapError Unauthorized
}
let signIn (user: string) : Flow<SessionError, string> =
flow {
do! authorizeForLogin user
return user
}
FsLiveDocsGeneratedPage12_4556FC416C2B.AuthErrorDeniedstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage12_4556FC416C2B.TokenErrorExpiredFsLiveDocsGeneratedPage12_4556FC416C2B.SessionErrorUnauthorizedTokenFailedProfileNotFoundauthorize: string -> Async<Result<unit,AuthError>>user: stringMicrosoft.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
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. 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
(=): '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.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
createToken: string -> Result<string,TokenError>authorizeForLogin: string -> Async<Result<unit,SessionError>>result: Result<unit,AuthError>(|>): '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.Core.ResultModuleContains operations for working with values of type . Choices and Results
mapError: ('TError -> 'U) -> Result<'T,'TError> -> Result<'T,'U>map f inp evaluates to match inp with Error x -> Error (f x) | Ok v -> Ok v. A function to apply to the Error result value. The input result. A result of the error value after applying the mapping function, or Ok if the input is Ok. Ok 1 |> Result.mapError (fun x -> "bar") // evaluates to Ok 1 Error "foo" |> Result.mapError (fun x -> "bar") // evaluates to Error "bar"
signIn: string -> Flow<SessionError,string>FlowA flow that requires no environment and can fail with a typed error.
flow: FlowBuilderThe universal flow { } computation expression.
The equivalent transformation for a Result uses Result.mapError directly. A Flow uses Flow.mapError. Assigning
an error to Option or Async<Option<_>> requires another shape-specific conversion.
Bind gives these supported bind sources one bind-site syntax. You choose whether to assign or map the error; the
Flow computation expression handles the source shape:
let startSession (maybeProfile: string option) : Flow<SessionError, string> =
flow {
let! profile = maybeProfile |> Bind.error ProfileNotFound
do! authorize profile |> Bind.mapError Unauthorized
return! createToken profile |> Bind.mapError TokenFailed
}
startSession: string option -> Flow<SessionError,string>maybeProfile: string optionstringAn abbreviation for the CLI type . Basic Types
optionThe type of optional values. When used from other CLI languages the empty option is the null value. Use the constructors Some and None to create values of this type. Use the values in the Option module to manipulate values of this type, or pattern match against the values directly. 'None' values will appear as the value null to other CLI languages. Instance methods on this type will appear as static methods to other CLI languages due to the use of null as a value representation. Options
FlowA flow that requires no environment and can fail with a typed error.
FsLiveDocsGeneratedPage12_4556FC416C2B.SessionErrorflow: FlowBuilderThe universal flow { } computation expression.
profile: 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.BindModuleCreates error-adaptation markers for let!, do!, and return! in flow { }.
error: 'error -> ^source -> BindError<'env,'error,'value>Assigns an error to a missing or unit-error source at a flow { } bind site. The error to use if the source fails. The source to adapt. A marker to use directly with let!, do!, or return! in flow { }. flow { let! user = maybeUser |> Bind.error InvalidUser do! Result.requireTrue () isValid |> Bind.error InvalidInput }
ProfileNotFoundauthorize: string -> Async<Result<unit,AuthError>>mapError: ('error1 -> 'error2) -> ^source -> BindError<'env,'error2,'value>Maps an existing source error at a flow { } bind site. The error mapping function. The source to adapt. A marker to use directly with let!, do!, or return! in flow { }. flow { do! authorize user |> Bind.mapError Unauthorized }
UnauthorizedcreateToken: string -> Result<string,TokenError>TokenFailedstartSession (Some "ada") |> Flow.run () |> shouldEqual (Exit.Success "token-ada")
startSession None |> Flow.run () |> shouldEqual (Exit.Failure(Cause.Fail ProfileNotFound))
startSession (Some "bob") |> Flow.run () |> shouldEqual (Exit.Failure(Cause.Fail(Unauthorized(Denied "bob"))))
startSession: string option -> Flow<SessionError,string>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.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"
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.
ProfileNotFoundUnauthorizedDeniedHere, maybeProfile can be an Option or an asynchronous option source. Similarly, error mapping has the same form
for Result, Async<Result<_,_>>, and Flow.
Use Bind directly with let!, do!, or return!. It marks the error adaptation for that bind and does not run the
source. If the source already uses the workflow's error type, bind it without Bind.
Assign an error
Bind.error failure source assigns failure to a source that has no error value of its own. It preserves a present
or successful value. It turns None, ValueNone, or Error () into the supplied workflow error when flow { }
binds the source.
type User = { Name: string }
type LoginError = UserNotFound | InvalidPassword
let tryGetUser username : Async<User option> =
async { return if username = "ada" then Some { Name = username } else None }
let checkPassword password =
if System.String.IsNullOrWhiteSpace password then Error () else Ok ()
let login username password =
flow {
let! user =
tryGetUser username
|> Bind.error UserNotFound
do!
checkPassword password
|> Bind.error InvalidPassword
return user
}
FsLiveDocsGeneratedPage12_4556FC416C2B.UserName: stringstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage12_4556FC416C2B.LoginErrorUserNotFoundInvalidPasswordtryGetUser: string -> Async<User option>username: stringMicrosoft.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
optionThe type of optional values. When used from other CLI languages the empty option is the null value. Use the constructors Some and None to create values of this type. Use the values in the Option module to manipulate values of this type, or pattern match against the values directly. 'None' values will appear as the value null to other CLI languages. Instance methods on this type will appear as static methods to other CLI languages due to the use of null as a value representation. Options
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
(=): '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.
NoneThe representation of "No value"
checkPassword: string -> Result<unit,unit>password: stringSystemIsNullOrWhiteSpace: string -> boolIndicates whether a specified string is , empty, or consists only of white-space characters. The string to test. if the parameter is or , or if consists exclusively of white-space characters.
System.StringRepresents text as a sequence of UTF-16 code units.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
login: string -> string -> Flow<'a,LoginError,User>flow: FlowBuilderThe universal flow { } computation expression.
user: 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.BindModuleCreates error-adaptation markers for let!, do!, and return! in flow { }.
error: 'error -> ^source -> BindError<'env,'error,'value>Assigns an error to a missing or unit-error source at a flow { } bind site. The error to use if the source fails. The source to adapt. A marker to use directly with let!, do!, or return! in flow { }. flow { let! user = maybeUser |> Bind.error InvalidUser do! Result.requireTrue () isValid |> Bind.error InvalidInput }
login "ada" "secret" |> Flow.run () |> shouldEqual (Exit.Success { Name = "ada" })
login "bob" "secret" |> Flow.run () |> shouldEqual (Exit.Failure(Cause.Fail UserNotFound))
login "ada" " " |> Flow.run () |> shouldEqual (Exit.Failure(Cause.Fail InvalidPassword))
login: string -> string -> Flow<'a,LoginError,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.
Name: stringFailureThe 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.
UserNotFoundInvalidPasswordBind.error accepts these source types:
Option<'value>ValueOption<'value>Result<'value, unit>Flow<'env, unit, 'value>Async<Option<'value>>,Task<Option<'value>>, andValueTask<Option<'value>>Async<ValueOption<'value>>,Task<ValueOption<'value>>, andValueTask<ValueOption<'value>>Async<Result<'value, unit>>,Task<Result<'value, unit>>, andValueTask<Result<'value, unit>>
For a Boolean condition, first return a Result that states what failure looks like. Then bind that result directly
if it already uses the workflow error type, or apply Bind.error if it uses unit.
Map an error
Use Bind.mapError when the source has a meaningful error that must be translated to the surrounding workflow's
error type.
With authorize and createToken from the start of this page:
let issueToken (user: string) : Flow<SessionError, string> =
flow {
do!
authorize user
|> Bind.mapError Unauthorized
return!
createToken user
|> Bind.mapError TokenFailed
}
issueToken: string -> Flow<SessionError,string>user: stringstringAn abbreviation for the CLI type . Basic Types
FlowA flow that requires no environment and can fail with a typed error.
FsLiveDocsGeneratedPage12_4556FC416C2B.SessionErrorflow: FlowBuilderThe universal flow { } computation expression.
authorize: string -> Async<Result<unit,AuthError>>(|>): '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.BindModuleCreates error-adaptation markers for let!, do!, and return! in flow { }.
mapError: ('error1 -> 'error2) -> ^source -> BindError<'env,'error2,'value>Maps an existing source error at a flow { } bind site. The error mapping function. The source to adapt. A marker to use directly with let!, do!, or return! in flow { }. flow { do! authorize user |> Bind.mapError Unauthorized }
UnauthorizedcreateToken: string -> Result<string,TokenError>TokenFailedissueToken "ada" |> Flow.run () |> shouldEqual (Exit.Success "token-ada")
issueToken "bob" |> Flow.run () |> shouldEqual (Exit.Failure(Cause.Fail(Unauthorized(Denied "bob"))))
issueToken: string -> Flow<SessionError,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
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.
UnauthorizedDeniedBind.mapError accepts these source types:
Result<'value, 'error>Flow<'env, 'error, 'value>Async<Result<'value, 'error>>Task<Result<'value, 'error>>ValueTask<Result<'value, 'error>>
Use a marker only at a bind site
The value returned by Bind.error or Bind.mapError is a marker for the Flow computation expression. It is not a
general-purpose Result or Flow transformation, so it only compiles directly on the right side of let!, do!, or return!:
let atBindSites (maybeUser: string option) : Flow<SessionError, string> =
flow {
let! user = maybeUser |> Bind.error ProfileNotFound
return! createToken user |> Bind.mapError TokenFailed
}
atBindSites: string option -> Flow<SessionError,string>maybeUser: string optionstringAn abbreviation for the CLI type . Basic Types
optionThe type of optional values. When used from other CLI languages the empty option is the null value. Use the constructors Some and None to create values of this type. Use the values in the Option module to manipulate values of this type, or pattern match against the values directly. 'None' values will appear as the value null to other CLI languages. Instance methods on this type will appear as static methods to other CLI languages due to the use of null as a value representation. Options
FlowA flow that requires no environment and can fail with a typed error.
FsLiveDocsGeneratedPage12_4556FC416C2B.SessionErrorflow: FlowBuilderThe universal flow { } computation expression.
user: 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.BindModuleCreates error-adaptation markers for let!, do!, and return! in flow { }.
error: 'error -> ^source -> BindError<'env,'error,'value>Assigns an error to a missing or unit-error source at a flow { } bind site. The error to use if the source fails. The source to adapt. A marker to use directly with let!, do!, or return! in flow { }. flow { let! user = maybeUser |> Bind.error InvalidUser do! Result.requireTrue () isValid |> Bind.error InvalidInput }
ProfileNotFoundcreateToken: string -> Result<string,TokenError>mapError: ('error1 -> 'error2) -> ^source -> BindError<'env,'error2,'value>Maps an existing source error at a flow { } bind site. The error mapping function. The source to adapt. A marker to use directly with let!, do!, or return! in flow { }. flow { do! authorize user |> Bind.mapError Unauthorized }
TokenFailedOutside flow { }, use functions for the source type, such as Result.mapError, Option.toResult, or
Flow.mapError.
Raw Task and ValueTask values still require an explicit
Task interop adapter. Bind changes an error at a computation-expression
bind; it does not change when an operation starts.
Choose between Bind and Policy
Use Bind for a one-time error adaptation at a specific bind site. Use a
Policy for a named, reusable verification rule that can read the workflow environment,
compose with other rules, or be enabled by the environment.

