Dive C1.09.3 · the lab
Build your own
A builder is two methods. Write them, and let! means whatever you say it means.
This is the chapter's lab. A computation expression needs only a builder object
with the right methods, so a working one fits in three lines: a class with Bind and
Return, and an instance to name the block. The payoff is the short-circuit: define
Bind as Option.bind, and a None
anywhere in the chain stops the rest — Bind never calls the continuation, so the whole
block is None without a single if. Build it, then step
through it.
Three lines of builder
A builder is a class with the methods the compiler calls. For let! and
return over Option, that is two methods.
Bind takes an option and a continuation; Option.bind already
has exactly the shape needed. Return wraps a value in
Some. Instantiate the class once and name it option.
type OptionBuilder() = member _.Bind(o, f) = Option.bind f o member _.Return(x) = Some x let option = OptionBuilder()
With option in scope, the block runs top to bottom: bind a,
bind b, return their sum. Every Some means the chain
continues; the result is Some 7.
option { let! a = Some 3 let! b = Some 4 return a + b } // val it : int option = Some 7
- Bind(o, f)
Option.bind f o— runfonly whenoisSome.- Return(x)
Some x— wrap the final value back in an option.- let option
- one instance of the builder; the name that goes in front of the braces.
- Some 7
- both binds were
Some, so the chain ran toReturn.
Where None stops the chain
Swap the second Some 4 for None and the block is
None. The reason is in Option.bind: given
None, it returns None and never calls the continuation.
The second Bind is where the chain is cut — Return is never
reached.
option { let! a = Some 3 ; let! b = None ; return a + b } · the second Bind cuts the chain
The short-circuit is not a special case in the block — it is what Bind does
with None. The builder carries the logic, so the block stays a plain sequence of
steps.
Step the Bind chain
Run the block option { let! a = Some 3 ; let! b = ? ; return a + b }. Set the second
value, then step through the calls the builder makes. With b = Some 4 the chain runs
to Return and gives Some 7; with
b = None the second Bind returns
None and the steps after it are cut. The outcome for each choice is in the markup
already — the steps walk it.
option { let! a = Some 3 ; let! b = ? ; return a + b } · choose b, then step
- Bind(Some 3, fun a -> …) a is Some 3 · continue
- Bind(b, fun b -> …) b is Some 4 · continue
- Return(a + b) 3 + 4 wrapped in Some
- Some 7 the block's value
Same block, two outcomes, no branch in sight. The builder you wrote in three lines governs whether the chain runs on or stops — that is the whole idea of a computation expression.
A builder you wrote
Bind = Option.bind, Return = Some — three lines.
A block that short-circuits
A None cuts the chain; Some 7 or None, no if.
Where this closes
That closes the chapter. A value has a type, a function transforms it, the pipe wires functions together,
pattern matching branches on structure, unions and records shape the data, collections hold it, and
Option and Result carry absence and failure as ordinary
values. The computation expression takes all of that and hands you the syntax to write your own small
language over it — exactly as seq, async, and the
option builder above all do.
References
Sources
- Microsoft — F# documentation — writing custom computation expressions and the builder methods.
- The F# Language Specification — the
Bind/Returncontract a builder must satisfy. - FSharp.Core API reference —
Option.bindand theOptionmodule.
Related in this course
- C1.09 — Computation expressions — the module hub this lab closes.
- C1 — F# Language — the chapter, end to end.
- Elixir — Pipeline lab — the BEAM course's functional lab, the same build-it-yourself close.