StringBuffer - An F# string builder

StringBuffer - An F# string builder
Photo by Shubham Dhage / Unsplash

A few years ago (I know, I really should update this blog more often) I asked WTF is...a computation expression. Well, I'm happy to say in the meantime I discovered a great use for one that's reusable, and have finally released it as a library.

StringBuffer 1.0.1
An F# library that provides an easy way to write code that writes code

Introducing StringBuffer, a powerful and easy to use StringBuilder alternative for building blocks of text in F#. It's geared primarily towards creating code, having support for easily creating indented blocks to support whitespace sensitive languages like F# and YAML.

Text templating in .NET is dominated by T4 which is great if you're using C# or Visual Basic but it has no support for F#.

The library provides two computation expressions that each produce a string of text, with line breaks, and the indentation you specify.

The first is stringBuffer, the second indent which works in exactly the same way but starts off by increasing the indent before writing any text. It means that you can write something like this

stringBuffer {
    "let square x ="
    indent {
        "x * x"
    }
}

and it will produce this

let square x =
    x * x

Doesn't look too impressive with an example like that but we can use this in some interesting ways. Not only will it take a text value, but it will take a sequence of them too, and we can even perform operations on them to allow us to write a simple program.

let namespaces = seq {
    "System"
    "System.IO"
}

let formattedCode = stringBuffer {
    namespaces |> Seq.map (fun ns -> "open " + ns)

    ""
    "module MyModule"
    indent {
        "let LifeUniverseAndEverything () ="
        indent {
            "42"
        }
    }
}

This outputs the following

open System
open System.IO

module MyModule
    let LifeUniverseAndEverything () =
        42

This makes it very easy to generate correctly formatted code and means we finally have a better way to solve this ancient problem

let range = [1 .. 100]

let writeLine (x : int) =
    if System.Math.Pow(-1, x) > 0 then
        sprintf "if x = %i then true else" x
    else
        sprintf "if x = %i then false else" x

let isEven = stringBuffer {
    "let isEven x ="
    indent {
        range |> Seq.map writeLine
        sprintf "failwith \"There's no numbers past %i\"" (range |> List.last)
    }
}
It's exactly what you'd expect, just long
let isEven x =
    if x = 1 then false else
    if x = 2 then true else
    if x = 3 then false else
    if x = 4 then true else
    if x = 5 then false else
    if x = 6 then true else
    if x = 7 then false else
    if x = 8 then true else
    if x = 9 then false else
    if x = 10 then true else
    if x = 11 then false else
    if x = 12 then true else
    if x = 13 then false else
    if x = 14 then true else
    if x = 15 then false else
    if x = 16 then true else
    if x = 17 then false else
    if x = 18 then true else
    if x = 19 then false else
    if x = 20 then true else
    if x = 21 then false else
    if x = 22 then true else
    if x = 23 then false else
    if x = 24 then true else
    if x = 25 then false else
    if x = 26 then true else
    if x = 27 then false else
    if x = 28 then true else
    if x = 29 then false else
    if x = 30 then true else
    if x = 31 then false else
    if x = 32 then true else
    if x = 33 then false else
    if x = 34 then true else
    if x = 35 then false else
    if x = 36 then true else
    if x = 37 then false else
    if x = 38 then true else
    if x = 39 then false else
    if x = 40 then true else
    if x = 41 then false else
    if x = 42 then true else
    if x = 43 then false else
    if x = 44 then true else
    if x = 45 then false else
    if x = 46 then true else
    if x = 47 then false else
    if x = 48 then true else
    if x = 49 then false else
    if x = 50 then true else
    if x = 51 then false else
    if x = 52 then true else
    if x = 53 then false else
    if x = 54 then true else
    if x = 55 then false else
    if x = 56 then true else
    if x = 57 then false else
    if x = 58 then true else
    if x = 59 then false else
    if x = 60 then true else
    if x = 61 then false else
    if x = 62 then true else
    if x = 63 then false else
    if x = 64 then true else
    if x = 65 then false else
    if x = 66 then true else
    if x = 67 then false else
    if x = 68 then true else
    if x = 69 then false else
    if x = 70 then true else
    if x = 71 then false else
    if x = 72 then true else
    if x = 73 then false else
    if x = 74 then true else
    if x = 75 then false else
    if x = 76 then true else
    if x = 77 then false else
    if x = 78 then true else
    if x = 79 then false else
    if x = 80 then true else
    if x = 81 then false else
    if x = 82 then true else
    if x = 83 then false else
    if x = 84 then true else
    if x = 85 then false else
    if x = 86 then true else
    if x = 87 then false else
    if x = 88 then true else
    if x = 89 then false else
    if x = 90 then true else
    if x = 91 then false else
    if x = 92 then true else
    if x = 93 then false else
    if x = 94 then true else
    if x = 95 then false else
    if x = 96 then true else
    if x = 97 then false else
    if x = 98 then true else
    if x = 99 then false else
    if x = 100 then true else
    failwith "There's no numbers past 100"