"This control construct may only be used if the computation expression builder defines a 'Zero' method" 与 F# 的含义

meaning of "This control construct may only be used if the computation expression builder defines a 'Zero' method" with F#

我有这个代码:

Ok stringBuffer {
    let r = get some list....
    match r with
    | [] -> "no active tasks"
    | r  -> String.Join("\n", r)
}

stringBuffer 定义为:

[<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

我不是 StringBuffer 模块的作者。我经常使用它,因为它使得在 F#

中使用 StringBuilder 超级方便

我可以轻松混合字符串和逻辑:

stringBuffer {
    "hello"
    if x = 3 then "world"
}

但是,在这个 post 开头的示例中,我收到以下编译错误:

[FS0708] This control construct may only be used if the computation expression builder defines a 'Zero' method.

在计算表达式中,零方法被定义为忽略所以问题可能在那里。但我的问题是:

这个错误是关于什么的?为什么这个特定用例需要实施零方法?

我的理解是,如果表达式 return 什么都没有,则使用零方法,因为它对计算表达式无效;但是既然我指定了一个字符串,为什么这个执行路径return什么都没有?


编辑:

错误截图(Rider/dotnet 5)

现在,错误减少到这种情况:

Ok stringBuffer {
    let r = get some list....
    match r with
    | [] -> "no active tasks"
    | r  -> String.Join("\n", r)
}

触发错误,但是

let s = 
    stringBuffer {
        let r = get some list....
        match r with
        | [] -> "no active tasks"
        | r  -> String.Join("\n", r)
    }
Ok s

没有

如果我添加括号,它对我有用:

let result =
    Ok (stringBuffer {
        let r = [1;2;3]
        match r with
        | [] -> "no active tasks"
        | r  -> String.Join("\n", r)
    })
printfn "%A" result

没有括号,会出现“零”错误消息,因为函数应用程序是左关联的,因此编译器认为您的意思是这样的:(Ok stringBuffer) { "str" }。由于 Ok stringBuffer 是缺少 Zero 成员的表达式,因此无法编译。

有趣的是,如果您定义自己的 Ok 运算符 returns 一个有效的构建器,它会编译得很好:

let Ok (sb : StringBufferBuilder) = sb
let result = Ok stringBuffer { "hello "; "world" }   // produces: "hello world"