Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

f#

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?

like image 754
Daniel Avatar asked Feb 15 '10 15:02

Daniel


People also ask

Is StringBuilder a Builder pattern?

The StringBuilder class has the word "builder" in its name, but it has nothing to do with telescoping constructors, it simply helps us collect all the data that we need to pass to the constructor of an immutable object.

How is StringBuilder different from Builder pattern?

Different structure. String Builder has two collaborators, Builder pattern has four collaborators.

Why we use Builder pattern in java?

Builder pattern aims to “Separate the construction of a complex object from its representation so that the same construction process can create different representations.” It is used to construct a complex object step by step and the final step will return the object.

What is a builder function?

What is a builder function? 🤔 A builder is a Flutter design pattern in which the construction code of a widget is defined outside of its class. Builder functions are callback interfaces that pass data (often layout-specific) to the parent widget which returns a child based on that data.


1 Answers

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

  1. 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.
  2. 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.

like image 129
kvb Avatar answered Oct 11 '22 10:10

kvb