Reading — step 1 of 5
Learn
Computation expressions are F#'s general-purpose do-notation. Build a custom "effect" type, then write code that looks imperative but threads it through.
The built-in async { ... } is a computation expression. So is seq { ... }, query { ... }. You can define your own.
Minimal builder for Option:
type OptionBuilder() =
member _.Return(x) = Some x
member _.Bind(m, f) =
match m with
| Some v -> f v
| None -> None
let option = OptionBuilder()
Use it — looks like sequential code:
let divide a b =
if b = 0 then None else Some (a / b)
let compute () =
option {
let! x = divide 100 5 // 20
let! y = divide x 2 // 10
let! z = divide y 0 // None — short circuits!
return z + 1
}
compute () // None
let! is the unwrap operator — calls Bind under the hood. return calls Return.
More builder methods unlock more syntax:
Return(x)— forreturnReturnFrom(m)— forreturn!Bind(m, f)— forlet!Combine(m1, m2)— for sequence of statementsZero()— empty bodyDelay(f)— defer evaluationFor(seq, body)— for loopsWhile(cond, body)— while loopsTryWith(body, handler)/TryFinallyUsing(disposable, body)—use!
The more you implement, the more like F# code your custom expression looks.
Real-world examples:
async— async/awaitseq— generatorstask(F# 6+) — interop with C# Task- Saturn / Giraffe — HTTP routing DSL
- ZeroFormatter / FsLexYacc — parser combinators
Comparison to Haskell: F# computation expressions are ~equivalent to do-notation over monads. F# is more flexible (Combine, Zero — no monad laws enforced) and less mathy.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…