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)

enter image description here

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

1

There are 1 best solutions below

2
On BEST ANSWER

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"