A computation expression is code in braces after the name of a builder: async { … }, seq { … }, task { … }, query { … } or one you write. Inside the braces the ordinary keywords are given a new meaning by the builder, and extra keywords with a ! are available. The compiler rewrites the block into calls of the builder’s methods, so the same syntax serves sequences, asynchronous code, optional values, parsers and queries.
The keywords
| Form | Meaning | Builder method it calls |
|---|---|---|
let x = e | an ordinary binding | none |
let! p = e | run the computation e and bind its result | Bind(e, fun p -> …) |
use! p = e | like let! and dispose the result at the end | Bind then Using |
and! p = e | run another computation in parallel with the one above | MergeSources (and BindN, MergeSourcesN) |
do! e | run a computation of unit for its effect | Bind(e, fun () -> …) |
match! e with … | run e and match on the result | Bind, then the match |
while! c do … | a loop whose condition is itself a computation (while! is a token of the lexer; its translation was not found in the documentation read) | not checked |
return e | produce a final value | Return(e) |
return! e | produce the result of another computation | ReturnFrom(e) (ReturnFromFinal in tail position) |
yield e | produce one element of a sequence | Yield(e) |
yield! e | produce all elements of another sequence | YieldFrom(e) (YieldFromFinal) |
use p = e | dispose at the end of the block | Using(e, fun p -> …) |
if c then a (no else) | an optional step | Zero() for the missing branch |
a; b (two steps in a row) | sequencing | Combine(a, Delay(fun () -> b)) |
for x in xs do … | a loop | For(xs, fun x -> …) |
while c do … | a loop | While(fun () -> c, Delay(…)) |
try … with …, try … finally … | exception handling | TryWith, TryFinally |
The documentation also lists the method Delay (wraps the rest of a block as a function), Run (executes the whole computation), BindReturn and BindNReturn (efficient let! … return), Quote (gets the block as a quotation) and the pairs ReturnFromFinal and YieldFromFinal (F# 10, tail calls). An empty body builder { } is allowed from F# 9 and calls Zero.
The four built-in kinds
// a sequence: lazy, with yield
let evens = seq { for i in 1 .. 10 do if i % 2 = 0 then yield i }
let squares = [ for i in 1 .. 3 -> i * i ] // list and array comprehensions use the same forms
// async: a cold computation run later
let fetchLength (url: string) : Async<int> =
async {
use client = new System.Net.Http.HttpClient()
let! html = client.GetStringAsync(url) |> Async.AwaitTask
return html.Length
}
let n = fetchLength "https://example.org" |> Async.RunSynchronously
// task: a hot .NET Task, started at once (F# 6)
let work () : System.Threading.Tasks.Task<int> =
task {
let! a = System.Threading.Tasks.Task.FromResult 1
and! b = System.Threading.Tasks.Task.FromResult 2 // and! in task: F# 10
return a + b
}
// query: operators over IQueryable and sequences
let adults =
query {
for p in people do
where (p.Age >= 18)
sortBy p.Name
select p.Name
}
- Sequence expressions
seq { … }, and list and array expressions[ … ]and[| … |], are lazy (the sequence form) or eager (the others);yieldis optional when the value is on its own (implicit yields, F# 4.7). async { … }builds anAsync<'T>that does nothing until run withAsync.RunSynchronously,Async.Start,Async.StartImmediateor similar;Async.Parallelcombines several;Async.AwaitTaskandAsync.StartAsTaskconvert from and to tasks. It carries a cancellation token implicitly.task { … }(F# 6) builds a .NETTask<'T>that starts to run at once up to its first await; it does not propagate a cancellation token implicitly and does not support asynchronous tail calls (a recursivereturn!can overflow the stack unless it waits in between).backgroundTask { … }runs on the thread pool; a task block can be made aValueTaskwith|> ValueTask<int>.query { … }translates to LINQ. Its operators are custom operations:all averageBy averageByNullable contains count distinct exactlyOne exactlyOneOrDefault exists find groupBy groupJoin groupValBy head headOrDefault join last lastOrDefault leftOuterJoin maxBy maxByNullable minBy minByNullable nth select skip skipWhile sortBy sortByDescending sortByNullable sortByNullableDescending sumBy sumByNullable take takeWhile thenBy thenByDescending thenByNullable thenByNullableDescending where.
Writing a builder
type MaybeBuilder() =
member _.Bind(m: 'a option, f: 'a -> 'b option) =
match m with
| Some x -> f x
| None -> None
member _.Return(x) = Some x
member _.ReturnFrom(m: 'a option) = m
member _.Zero() = None
let maybe = MaybeBuilder()
let sum a b =
maybe {
let! x = Int32.TryParse a |> function true, v -> Some v | _ -> None
let! y = Int32.TryParse b |> function true, v -> Some v | _ -> None
return x + y
}
type RetryBuilder(times: int) =
member _.Bind(x, f) = f x
member _.Return(x) = x
[<CustomOperation("log", MaintainsVariableSpace = true)>]
member _.Log(state, message: string) = printfn "%s" message; state
[<CustomOperation>] adds a keyword.A custom operation is a builder method marked with [<CustomOperation("name")>]; the name becomes a keyword inside the block, as where and select are in query. F# 6 allows overloads of custom operations, and F# 9 a parameterless CustomOperationAttribute constructor. InlineIfLambda and resumable code (F# 6) let builder authors remove the overhead of closures; F# 11 adds a runtime-async mode for builder authors and reraise() inside the block.
In the viewer
The words with a bang are coloured as keywords with the bang included. async, seq, task, backgroundTask and query are coloured as library functions; the names of custom operations (where, select) and of your own builders are not known to the scanner and stay plain unless followed directly by a bracket.
Not described
Known, not yet covered
- Reference: the full translation rules of the specification (how
Delay,Run,CombineandZeroare placed,returnafterand!, error cases), the rest of theasyncandtaskpages (cancellation, bounded concurrency,Asyncprimitives inFSharp.Control.CommonExtensions), theMailboxProcessorandEventclasses, resumable code, and the query operator semantics (what each operator does and the SQL comparison table) are not described. - Reference: the list of builder methods and the translation table were taken from the documentation’s Computation Expressions page; the examples were written for this page and not compiled (the
Int32.TryParseline needsopen System).
See also
- The standard library – the
Async,SeqandOptionmodules and the builders - Lexical structure – the words with a bang