jonnifyfsharp

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.

C1.09.3 · part 1

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 — run f only when o is Some.
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 to Return.
C1.09.3 · part 2

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

Bind(Some 3, …) Some — continue Bind(None, …) None — stop here cut Return(a + b) never reached the whole block = None one None cuts the chain — no if needed
Bind(Some 3, …) → Bind(None, …) returns None · Return is never called

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.

the lab

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

let! b =
  1. Bind(Some 3, fun a -> …) a is Some 3 · continue
  2. Bind(b, fun b -> …) b is Some 4 · continue
  3. Return(a + b) 3 + 4 wrapped in Some
  4. Some 7 the block's value
b = Some 4 · step 1 of 4 · Bind(Some 3, …) · a is Some 3, the chain continues

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

Related in this course