I have this code:
Ok stringBuffer {
let r = get some list....
match r with
| [] -> "no active tasks"
| r -> String.Join("\n", r)
}
with stringBuffer defined as:
[<AutoOpen>]
module StringBuffer =
type StringBuffer = StringBuilder -> unit
type StringBufferBuilder () =
member inline this.Yield (txt: string) = fun (b: StringBuilder) -> Printf.bprintf b "%s" txt
member inline this.Yield (c: char) = fun (b: StringBuilder) -> Printf.bprintf b "%c" c
member inline this.Yield (strings: #seq<string>) = fun (b: StringBuilder) -> for s in strings do Printf.bprintf b "%s\n" s
member inline this.YieldFrom (f: StringBuffer) = f
member this.Combine (f, g) = fun (b: StringBuilder) -> f b; g b
member this.Delay f = fun (b: StringBuilder) -> (f()) b
member this.Zero () = ignore
member this.For (xs: 'a seq, f: 'a -> StringBuffer) =
fun (b: StringBuilder) ->
use e = xs.GetEnumerator ()
while e.MoveNext() do
(f e.Current) b
member this.While (p: unit -> bool, f: StringBuffer) =
fun (b: StringBuilder) -> while p () do f b
member this.Run (f: StringBuffer) =
let b = StringBuilder()
do f b
b.ToString()
let stringBuffer = StringBufferBuilder()
type StringBufferBuilder with
member inline this.Yield (b: byte) = fun (sb: StringBuilder) -> Printf.bprintf sb "%02x " b
I am not the author of the StringBuffer module. I'm using it regularly as it makes using StringBuilder super convenient to use in F#
I can mix strings and logic easily:
stringBuffer {
"hello"
if x = 3 then "world"
}
but, in the example at the beginning of this post, I am getting the following compilation error:
[FS0708] This control construct may only be used if the computation expression builder defines a 'Zero' method.
In the computation expression, the Zero method is defined as ignore so the problem is probably there. But my question is:
What is this error about? why does this specific use case require the implementation of the Zero method?
My understanding is that the Zero method is used if the expression would return nothing, as it is not valid for a computation expression; But since I specify a string, why would this execution path return nothing?
Edit:
Screenshot of the error (Rider / dotnet 5)
Now, the error is reduced to this scenario:
Ok stringBuffer {
let r = get some list....
match r with
| [] -> "no active tasks"
| r -> String.Join("\n", r)
}
trigger the error, but
let s =
stringBuffer {
let r = get some list....
match r with
| [] -> "no active tasks"
| r -> String.Join("\n", r)
}
Ok s
does not
It works for me if I add parens:
let result =
Ok (stringBuffer {
let r = [1;2;3]
match r with
| [] -> "no active tasks"
| r -> String.Join("\n", r)
})
printfn "%A" result
Without the parens, the "Zero" error message occurs because function application is left-associative, so the compiler thinks you mean something like this: (Ok stringBuffer) { "str" }
. Since Ok stringBuffer
is an expression that lacks a Zero
member, this does not compile.
Amusingly, if you define your own Ok
operator that returns a valid builder, it will compile fine:
let Ok (sb : StringBufferBuilder) = sb
let result = Ok stringBuffer { "hello "; "world" } // produces: "hello world"