StringBuffer - An F# string builder
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.
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"