Computation Expressions
You've already used several computation expressions: seq { },
list expressions, and (soon) async { } and task { }. They all share
one syntax — a builder name followed by a block — and a set of
special keywords whose meaning the builder defines:
| Keyword | Meaning inside the block |
|---|---|
let! |
"unwrap" a wrapped value (await a task, take a Some, ...) |
do! |
like let! for a step that returns nothing useful |
return |
wrap a plain value as the result |
return! |
return an already-wrapped value |
yield / yield! |
produce items (sequences and lists) |
use! |
let! plus automatic disposal |
match! |
let! followed by match |
The idea: you write straight-line code, and the builder handles the
plumbing — laziness for seq, waiting for task, early exit for
option or result.
The same code, with and without
Suppose three lookups can each fail by returning None:
let users = Map.ofList [ 1, "ada"; 2, "grace" ]
let emails = Map.ofList [ ("ada", "ada@example.com") ]
let domains = Map.ofList [ ("example.com", "Example Inc.") ]
let domainOf (email: string) = email.Split('@') |> Array.tryItem 1
// Nested matches: correct but hard to read
let companyNested id =
match Map.tryFind id users with
| None -> None
| Some user ->
match Map.tryFind user emails with
| None -> None
| Some email ->
match domainOf email with
| None -> None
| Some domain -> Map.tryFind domain domains
// Option.bind: flatter
let companyBind id =
Map.tryFind id users
|> Option.bind (fun u -> Map.tryFind u emails)
|> Option.bind domainOf
|> Option.bind (fun d -> Map.tryFind d domains)
printfn "%A %A" (companyNested 1) (companyBind 1)
printfn "%A %A" (companyNested 2) (companyBind 2)
Some "Example Inc." Some "Example Inc."
None None
With an option computation expression (you'll write the builder in
the next lesson), the same logic reads like ordinary code:
let company id =
option {
let! user = Map.tryFind id users
let! email = Map.tryFind user emails
let! domain = domainOf email
return! Map.tryFind domain domains
}
Each let! means "if this is Some x, continue with x; if it's
None, stop and return None".
Built-in builders
seq { },[ ],[| |]— sequences, lists, arraysasync { }— F# asynchronous workflowstask { }— .NETTasks, for interop and high-performance I/Olazy ( )isn't a builder, but is relatedquery { }— LINQ-style queries, used with databases
let evensSquared =
[ for i in 1 .. 10 do
if i % 2 = 0 then
let sq = i * i
yield sq ]
let q =
query {
for n in [ 5; 3; 8; 1 ] do
where (n > 2)
sortBy n
select (n * 10)
}
printfn "%A" evensSquared
printfn "%A" (List.ofSeq q)
[4; 16; 36; 64; 100]
[30; 50; 80]
Libraries add many more: result { } and validation { } from
FsToolkit.ErrorHandling, html { }-style builders for web views,
cancellableTask { }, and so on.