F# computation expressions have the syntax:
ident { cexpr }
Where ident
is the builder object (this syntax is taken from Don Syme's 2007 blog entry).
In all the examples I've seen, builder objects are singleton instances, and stateless to boot. Don gives the example of defining a builder object called attempt
:
let attempt = new AttemptBuilder()
My question: Why doesn't F# just use the AttemptBuilder
class directly in computation expressions? Surely the notation could be de-sugared to static method calls just as easily as instance method calls.
Using an instance value means that one could in theory instantiate multiple builder objects of the same class, presumably parameterised in some way, or even (heaven forbid) with mutable internal state. But I can't imagine how that would ever be useful.
Update: The syntax I quoted above suggests the builder must appear as a single identifier, which is misleading and probably reflects an earlier version of the language. The most recent F# 2.0 Language Specification defines the syntax as:
expr { comp-or-range-expr }
which makes it clear that any expression (that evaluates to a builder object) can be used as the first element of the construct.
Your assumption is correct; a builder instance can be parameterized, and parameters can be subsequently used throughout the computation.
I use this pattern for building a tree of mathematical proof to a certain computation. Each conclusion is a triple of a problem name, a computation result, and a N-tree of underlying conclusions (lemmas).
Let me provide with a small example, removing a proof tree, but retaining a problem name. Let's call it annotation as it seems more suitable.
type AnnotationBuilder(name: string) =
// Just ignore an original annotation upon binding
member this.Bind<'T> (x, f) = x |> snd |> f
member this.Return(a) = name, a
let annotated name = new AnnotationBuilder(name)
// Use
let ultimateAnswer = annotated "Ultimate Question of Life, the Universe, and Everything" {
return 42
}
let result = annotated "My Favorite number" {
// a long computation goes here
// and you don't need to carry the annotation throughout the entire computation
let! x = ultimateAnswer
return x*10
}