Implementing the builder pattern (a la System.Text.StringBuilder) in F#

f#

Solution

I think that depending on your use cases you might be better off with an immutable implementation. The following example will statically enforce that any builder has its where, order, and group properties set exactly once before being built, although they can be set in any order:

type QueryBuilder<'t,'w,'o,'g> = 
  internal { where : 'w; order : 'o; group : 'g } with

let emptyBuilder = { where = (); order = (); group = () }

let addGroup (g:'t -> obj) (q:QueryBuilder<'t,_,_,unit>) : QueryBuilder<'t,_,_,_> =
  { where = q.where; order = q.order; group = g }

let addOrder (o:'t -> obj * bool) (q:QueryBuilder<'t,_,unit,_>) : QueryBuilder<'t,_,_,_> =
  { where = q.where; order = o; group = q.group }

let addWhere (w:'t -> bool) (q:QueryBuilder<'t,unit,_,_>) : QueryBuilder<'t,_,_,_> =
  { where = w; order = q.order; group = q.group }

let build (q:QueryBuilder<'t,'t->bool,'t->obj,'t->obj*bool>) =
  // build query from builder here, knowing that all components have been set

Obviously you may have to tweak this for your particular constraints, and to expose it to other languages you may want to use members on another class and delegates instead of let-bound functions and F# function types, but you get the picture.

UPDATE

Perhaps it's worth expanding on what I've done with a bit more description - the code is somewhat dense. There is nothing special about using record types; a normal immutable class would be just as good - the code would be a bit less concise but interop with other languages would probably work better. There are essentially two important features of my implementation

- Each of the methods for adding returns a new builder representing the current state. This is fairly straightforward, though it is obviously different from how the Builder pattern is normally implemented.

- By using additional generic type parameters you can enforce non-trivial invariants, such as requiring each of several different properties to be specified exactly once before using the Builder. This may be overkill for some applications, and is a bit tricky. It is only possible with an immutable Builder, since we might need to return a Builder with different type parameters after an operation.

In the example above, this sequence of operations would be allowed by the type system:

let query = 
  emtpyBuilder
  |> addGroup ...
  |> addOrder ...
  |> addWhere ...
  |> build

whereas this one wouldn't, because it never sets the order:

let query =
  emptyBuilder
  |> addGroup ...
  |> addWhere ...
  |> build

As I said, this may be overkill for your application, but it is only possible because we're using immutable builders.

Problem

Mutating state is at the center of the builder pattern. Is there an idiomatic way to implement the internals of such a class in F# that will reduce/eliminate mutable state while retaining the usual interface (this class will be used mostly from other .NET languages)? Here's a naive implementation: ``` type QueryBuilder<'T>() = //' let where = ref None let orderBy = ref None let groupBy = ref None member x.Where(cond) = match !where with | None -> where := Some(cond) | _ -> invalidOp "Multiple WHERE clauses are not permitted" // members OrderBy and GroupBy implemented similarly ``` One idea is to create a record type to store the internals, and use copy and update expressions. ``` type private QueryBuilderSpec<'T> = //' { Where : ('T -> bool) option; //' OrderBy : (('T -> obj) * bool) list; //' GroupBy : ('T -> obj) list } //' type QueryBuilder<'T>() = //' let spec = ref None member x.Where(cond) = match !spec with | None -> spec := Some({ Where = Some(cond); OrderBy = []; GroupBy = [] }) | Some({ Where = None; OrderBy = _; GroupBy = _} as s) -> spec := Some({ s with Where = Some(cond) }) | _ -> invalidOp "Multiple WHERE clauses are not permitted" // members OrderBy and GroupBy implemented similarly ``` This all seems a bit clunky, and maybe that should be expected when trying to implement an imperative pattern in F#. Is there a better way to do this, again, retaining the usual builder interface for the sake of imperative languages?

Original source