Search code examples
f#functional-programmingcomputation-expression

F# "Stateful" Computation Expression


I'm currently learning F# and hitting a few stumbling blocks; I think a lot of it is learning to think functionally.

One of the things I'm learning at the moment are computation expressions, and I want to be able to define a computation expression that handles some tracking state, e.g:

let myOptions = optionListBuilder {
    let! opt1 = {name="a";value=10}
    let! opt2 = {name="b";value=12}
}

I want to be able to have it so that myOptions is a Option<'T> list, so each let! bind operation effectively causes the builder to "track" the defined options as it goes along.

I don't want to have to do it using mutable state - e.g. having a list maintained by the builder and updated with each bind call.

Is there some way of having it so that this is possible?


Update: The resultant Option<'T> list type is just representative, in reality I'll likely have an OptionGroup<'T> type to contain a list as well as some additional information - so as Daniel mentioned below, I could use a list comprehension for a simple list.


Solution

  • I wrote a string builder computation expression here.

    open System.Text
    
    type StringBuilderUnion =
    | Builder of StringBuilder
    | StringItem of string
    
    let build sb =
        sb.ToString()
    
    type StringBuilderCE () =
        member __.Yield (txt : string) = StringItem(txt)
        member __.Yield (c : char) = StringItem(c.ToString())
        member __.Combine(f,g) = Builder(match f,g with
                                         | Builder(F),   Builder(G)   ->F.Append(G.ToString())
                                         | Builder(F),   StringItem(G)->F.Append(G)
                                         | StringItem(F),Builder(G)   ->G.Append(F)
                                         | StringItem(F),StringItem(G)->StringBuilder(F).Append(G))
        member __.Delay f = f()
        member __.Zero () = StringItem("")
        member __.For (xs : 'a seq, f : 'a -> StringBuilderUnion) =
                        let sb = StringBuilder()
                        for item in xs do
                            match f item with
                            | StringItem(s)-> sb.Append(s)|>ignore
                            | Builder(b)-> sb.Append(b.ToString())|>ignore
                        Builder(sb)
    
    let builder1 = new StringBuilderCE ()
    

    Noticed the underlying type is immutable (the contained StringBuilder is mutable, but it doesn't have to be). Instead of updating the existing data, each yield combines the current state and the incoming input resulting in a new instance of StringBuilderUnion You could do this with an F# list since adding an element to the head of the list is merely the construction of a new value rather than mutating the existing values.

    Using the StringBuilderCE looks like this:

    //Create a function which builds a string from an list of bytes
    let bytes2hex (bytes : byte []) =
        string {
            for byte in bytes -> sprintf "%02x" byte
        } |> build
    
    //builds a string from four strings
    string {
            yield "one"
            yield "two"
            yield "three"
            yield "four"
        } |> build
    

    Noticed the yield instead of let! since I don't actually want to use the value inside the computation expression.