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
…