Railway-Oriented Programming

FsToolkit.ErrorHandling provides result { }, asyncResult { }, taskResult { }, and validation { } computation expressions plus helper functions. It's the standard library for this style.

// Two-track functions: success (Ok) and failure (Error)
type OrderError =
    | MissingName
    | InvalidQuantity of int
    | OutOfStock of string

let validateName (o: {| Name: string; Qty: int |}) =
    if o.Name = "" then Error MissingName else Ok o

let validateQty (o: {| Name: string; Qty: int |}) =
    if o.Qty <= 0 then Error (InvalidQuantity o.Qty) else Ok o

let checkStock (o: {| Name: string; Qty: int |}) =
    if o.Name = "unicorn" then Error (OutOfStock o.Name) else Ok o

// Switch combinators
let (>>=) result f = Result.bind f result          // bind operator
let (>=>) f g = fun x -> f x >>= g                  // Kleisli composition ("fish")

let validateOrder = validateName >=> validateQty >=> checkStock

validateOrder {| Name = "book"; Qty = 2 |}          // Ok {| Name = "book"; Qty = 2 |}
validateOrder {| Name = "unicorn"; Qty = 2 |}       // Error (OutOfStock "unicorn")

// Accumulating ALL errors (validation, applicative style)
let apply fResult xResult =
    match fResult, xResult with
    | Ok f, Ok x -> Ok (f x)
    | Error e, Ok _ -> Error e
    | Ok _, Error e -> Error e
    | Error e1, Error e2 -> Error (e1 @ e2)

let (<!>) = Result.map
let (<*>) = apply


…