Runnable Examples
Runnable Examples
This page shows the examples that are executed during the docs build, so the public docs stay tied to real code and observed output.
The examples below are built from the repository projects, run with the current source, and then written back into this page.
The code blocks keep the important API calls on the same lines as the values they bind, with trailing comments where that makes the signature easier to read. The examples prefer the normal direct-bind style inside computation expressions, so the docs reflect the recommended day-to-day usage.
Request Boundary Example
This example shows a request boundary that pulls a user from a database-like environment, threads a trace id through the request context, and reuses the same validation shape across Flow.
Run it:
FSFLOW_EXAMPLE=request-boundary dotnet run --project examples/FsFlow.Examples/FsFlow.Examples.fsproj --nologo
Source:
Source code:
module RequestBoundaryExample
open System
open System.Threading
open System.Threading.Tasks
open FsFlow
type User =
{ Id: int
Name: string }
type AppDb =
{ FindUser: int -> User option }
type RequestEnv =
{ TraceId: Guid
Prefix: string
Db: AppDb
LoadSuffix: Task<string> }
let validateName (name: string) : Result<string, string> =
Check.notBlank name
|> Check.orError "name is required"
let loadUser : Flow<RequestEnv, string, User> =
flow {
let! db = Flow.read _.Db // Flow<RequestEnv, string, AppDb>
let! user = db.FindUser 42 |> Flow.fromOption "user not found" // Flow<RequestEnv, string, User>
return user
}
let renderTrace : Flow<RequestEnv, string, string> =
flow {
let! env = Flow.env // Flow<RequestEnv, string, RequestEnv>
let! user = loadUser // Flow<RequestEnv, string, User>
let! validName = validateName user.Name // Flow<RequestEnv, string, string>
return $"{env.Prefix} [{env.TraceId}] {validName}"
}
let publishResponse : Flow<RequestEnv, string, string> =
flow {
let! env = Flow.env // Flow<RequestEnv, string, RequestEnv>
let! user = loadUser // Flow<RequestEnv, string, User>
let! suffix = env.LoadSuffix // Flow<RequestEnv, string, string>
return $"{env.Prefix} [{env.TraceId}] {user.Name}{suffix}"
}
let run () =
let environment =
{ TraceId = Guid.Parse "11111111-1111-1111-1111-111111111111"
Prefix = "Hello"
Db =
{ FindUser =
function
| 42 -> Some { Id = 42; Name = "Ada" }
| _ -> None }
LoadSuffix = Task.FromResult "!" }
let syncResult =
loadUser
|> Flow.run environment
|> fun t -> t.AsTask().GetAwaiter().GetResult()
let asyncResult =
renderTrace
|> Flow.run environment
|> fun t -> t.AsTask().GetAwaiter().GetResult()
let taskResult =
publishResponse
|> Flow.run environment
|> fun t -> t.AsTask().GetAwaiter().GetResult()
printfn "Flow result: %A" syncResult
printfn "Flow result: %A" asyncResult
printfn "Flow result: %A" taskResult
// Flow result: Ok { Id = 42; Name = "Ada" }
// Flow result: Ok "Hello [11111111-1111-1111-1111-111111111111] Ada"
// Flow result: Ok "Hello [11111111-1111-1111-1111-111111111111] Ada!"
Diagnostics Example
This example shows a JSON-shaped request boundary with a root-level error, nested child branches, and a display-friendly diagnostics tree.
Run it:
FSFLOW_EXAMPLE=diagnostics dotnet run --project examples/FsFlow.Examples/FsFlow.Examples.fsproj --nologo
Source:
Source code:
module DiagnosticsExample
open System.Text.Json
open FsFlow
type CustomerLine =
{ Name: string }
type CustomerAddress =
{ City: string }
type Customer =
{ Name: string
Address: CustomerAddress
Lines: CustomerLine list }
type CreateCustomerRequest =
{ RequestId: string
Customer: Customer }
type ApiError =
{ path: string
message: string }
type ApiErrorResponse =
{ errors: ApiError list }
let jsonOptions = JsonSerializerOptions(WriteIndented = true)
let validateAddress address =
validate.key "address" {
let! city =
validate.name "City" {
return! address.City |> Check.notBlank |> Check.orError "City required"
}
return { address with City = city }
}
let validateCustomer customer =
validate {
let! name =
validate.name "Name" {
return! customer.Name |> Check.notBlank |> Check.orError "Name required"
}
and! address = validateAddress customer.Address
and! lines =
validate.key "lines" {
return!
customer.Lines
|> Validation.traverseIndexed (fun index line ->
validate.name "Name" {
let! name =
line.Name |> Check.notBlank |> Check.orError $"Line {index} name required"
return { Name = name }
}
)
}
return
{ customer with
Name = name
Address = address
Lines = lines }
}
let renderPath (path: PathSegment list) =
path
|> List.map (function
| PathSegment.Key value
| PathSegment.Name value -> value
| PathSegment.Index index -> $"[{index}]")
|> String.concat "."
let toApiErrors (graph: Diagnostics<'error>) =
{ errors =
graph
|> Diagnostics.flatten
|> List.map (fun diagnostic ->
{ path = renderPath diagnostic.Path
message = string diagnostic.Error }) }
let validateCreateCustomerRequest request =
validate {
let! requestId =
validate.name "RequestId" {
return! request.RequestId |> Check.notBlank |> Check.orError "RequestId required"
}
and! customer =
validate.key "customer" {
return! validateCustomer request.Customer
}
return { request with RequestId = requestId; Customer = customer }
}
let run () =
let requestJson =
"""{
"requestId": "",
"customer": {
"name": "",
"address": { "city": "" },
"lines": [ { "name": "" } ]
}
}"""
let badRequest =
{ RequestId = ""
Customer =
{ Name = ""
Address = { City = "" }
Lines = [ { Name = "" } ] } }
let diagnosticsText =
validateCreateCustomerRequest badRequest
|> Validation.toResult
|> Result.mapError (toApiErrors >> fun payload -> JsonSerializer.Serialize(payload, jsonOptions))
|> function
| Ok _ -> "Ok"
| Error text -> text
printfn "Request JSON:\n%s" requestJson
printfn "API error JSON:\n%s" diagnosticsText
// Request JSON:
// {
// "requestId": "",
// "customer": {
// "name": "",
// "address": { "city": "" },
// "lines": [ { "name": "" } ]
// }
// }
// API error JSON:
// {
// "errors": [
// {
// "path": "customer.address.City",
// "message": "City required"
// },
// {
// "path": "customer.lines.[0].Name",
// "message": "Line 0 name required"
// },
// {
// "path": "customer.Name",
// "message": "Name required"
// },
// {
// "path": "RequestId",
// "message": "RequestId required"
// }
// ]
// }
CAPS Core Example
This example shows the sync-first FsFlow.Caps.Core surface: fixed and live capability providers, plus typed errors for missing and invalid environment variables.
Run it:
dotnet run --project examples/FsFlow.Caps.Core.Examples/FsFlow.Caps.Core.Examples.fsproj --nologo
Source:
Source code:
namespace FsFlow.Caps.Core.Examples
open System
open FsFlow
open FsFlow.Caps.Core
module CoreCapabilitiesExample =
type private AppCaps =
{
Clock: IClock
Random: IRandom
Guid: IGuid
EnvVars: IEnvironmentVariables
}
interface Needs<IClock> with member this.Dep = this.Clock
interface Needs<IRandom> with member this.Dep = this.Random
interface Needs<IGuid> with member this.Dep = this.Guid
interface Needs<IEnvironmentVariables> with member this.Dep = this.EnvVars
let private renderExit formatter result =
match result with
| Exit.Success value -> $"Ok {formatter value}"
| Exit.Failure (Cause.Fail error) -> $"Error {EnvironmentVariableErrors.describe error}"
| Exit.Failure cause -> $"Failure {cause}"
let run () =
let caps =
{
Clock = Clock.fromValue (DateTimeOffset(2026, 5, 10, 12, 0, 0, TimeSpan.Zero))
Random = Random.fromValue 7
Guid = Guid.fromValue (global.System.Guid.Parse "11111111-1111-1111-1111-111111111111")
EnvVars =
EnvironmentVariables.fromPairs
[ "FSFLOW_CAPS_PORT", "8080"
"FSFLOW_CAPS_ENABLED", "true"
"FSFLOW_CAPS_SESSION", "22222222-2222-2222-2222-222222222222"
"FSFLOW_CAPS_PORT_TEXT", "abc" ]
}
let run flow = Flow.run caps flow |> fun t -> t.AsTask().GetAwaiter().GetResult()
printfn "clock=%O" (run Clock.now)
printfn "random=%d" (run (Random.nextInt 0 10) |> function Exit.Success v -> v | _ -> -1)
printfn "guid=%O" (run Guid.newGuid)
printfn "port=%s" (renderExit string (run (EnvironmentVariable.getInt "FSFLOW_CAPS_PORT")))
printfn "enabled=%s" (renderExit string (run (EnvironmentVariable.getBool "FSFLOW_CAPS_ENABLED")))
printfn "session=%s" (renderExit string (run (EnvironmentVariable.getGuid "FSFLOW_CAPS_SESSION")))
printfn "missing=%s" (renderExit string (run (EnvironmentVariable.get "FSFLOW_CAPS_MISSING")))
printfn "invalid=%s" (renderExit string (run (EnvironmentVariable.getInt "FSFLOW_CAPS_PORT_TEXT")))
Playground Example
This example shows the same core boundary across Flow using the normal direct-bind style inside each computation expression.
Run it:
dotnet run --project examples/FsFlow.Playground/FsFlow.Playground.fsproj --nologo
Source:
Source code:
open System
open System.Threading
open System.Threading.Tasks
open FsFlow
type AppEnv =
{ Prefix: string
Name: string
LoadSuffix: Task<string> }
let greetingFlow : Flow<AppEnv, string, string> =
Flow.read (fun env -> $"{env.Prefix} {env.Name}") // Flow<AppEnv, string, string>
let greetingAsync : Flow<AppEnv, string, string> =
flow {
let! greeting = greetingFlow
return greeting.ToUpperInvariant()
}
let greetingTask : Flow<AppEnv, string, string> =
flow {
let! env = Flow.env // Flow<AppEnv, string, AppEnv>
let! greeting = greetingFlow // Flow<AppEnv, string, string>
let! suffix = env.LoadSuffix // Flow<AppEnv, string, string>
return $"{greeting}{suffix}"
}
[<EntryPoint>]
let main _ =
let env =
{ Prefix = "Hello"
Name = "Ada"
LoadSuffix = Task.FromResult "!" }
let syncResult =
greetingFlow
|> Flow.run env
|> fun t -> t.AsTask().GetAwaiter().GetResult()
let asyncResult =
greetingAsync
|> Flow.run env
|> fun t -> t.AsTask().GetAwaiter().GetResult()
let taskResult =
greetingTask
|> Flow.run env
|> fun t -> t.AsTask().GetAwaiter().GetResult()
printfn "Flow: %A" syncResult
printfn "Async: %A" asyncResult
printfn "Task: %A" taskResult
// Flow: Ok "Hello Ada"
// Async: Ok "HELLO ADA"
// Task: Ok "Hello Ada!"
0
Maintenance Example
This example shows smaller, focused shapes for maintenance and interop scenarios without switching away from the normal direct-bind style.
Run it:
dotnet run --project examples/FsFlow.MaintenanceExamples/FsFlow.MaintenanceExamples.fsproj --nologo
Source:
Source code:
open System
open System.Threading
open System.Threading.Tasks
open FsFlow
let runFlow label env workflow =
let result = Flow.run env workflow |> fun t -> t.AsTask().GetAwaiter().GetResult()
printfn "%s: %A" label result
let runAsyncExample label env workflow =
let result =
workflow
|> Flow.run env
|> fun t -> t.AsTask().GetAwaiter().GetResult()
printfn "%s: %A" label result
let runTaskExample label env workflow =
let result =
workflow
|> Flow.run env
|> fun t -> t.AsTask().GetAwaiter().GetResult()
printfn "%s: %A" label result
let syncExample : Flow<int, string, int> =
Flow.read id // Flow<int, string, int>
|> Flow.map ((+) 1)
let asyncExample : Flow<int, string, int> =
flow {
let! value = async { return 21 }
return value * 2
}
let taskExample : Flow<int, string, int> =
flow {
let! env = Flow.read id
let! suffix = Task.FromResult 5
return env + suffix
}
[<EntryPoint>]
let main _ =
runFlow "Flow" 20 syncExample
runAsyncExample "Async" 20 asyncExample
runTaskExample "Task" 20 taskExample
// Flow: Ok 21
// Async: Ok 42
// Task: Ok 25
0