F# "do" 语句作为块表达式

F# "do" statements as Block expressions

F# spec 描述 10.2.5 do 模块中的语句 ,在模块级别 do 语句可能具有属性,并且当其表达式的类型未计算为 unit 时,它将产生警告。显然,do 语句在其他位置的行为类似于 Block 表达式 (6.5.1),无论是带括号的还是 begin-end-block,同时仍然声明 unit 类型。

这使得do语句适用于控制确定性处置表达式的范围(6.6.4):

let d x =
    printf "new %s, " x
    { new System.IDisposable with
        member __.Dispose() = printf "disposing %s, " x }

let ab() =
    use a = d "a"
    use b = d "b"
    printf "a + b, "
ab(); printfn ""
// prints 'new a, new b, a + b, disposing b, disposing a, '

let aba() =
    use a = d "a"
    do  use b = d "b"
        printf "a + b, "
    printf "a, "
aba(); printfn ""
// prints 'new a, new b, a + b, disposing b, a, disposing a, '

如果规范中没有明确规定,是否还有其他任何东西可以防止 F# 编译器的维护者破坏这种用法?

我认为您示例中的 aba 函数实际上同时使用了 do 语句和块表达式。 do 的语法是 do <expr> 所以 do 语句的主体是一个单一的表达式。如果我们用括号写同样的东西:

let aba() =
  use a = d "a"
  do ( use b = d "b"
       printf "a + b, " )
  printf "a, "

然后确定性处置行为来自块表达式的通常确定性处置行为 - 而不是来自 do 块的某些未记录的 属性。

实际上您根本不需要 do 语句,使用括号就足够了:

let aba() =
    use a = d "a"
    (
        use b = d "b"
        printf "a + b, "
    )
    printf "a, "
aba(); printfn ""
// Prints "new a, new b, a + b, disposing b, a, disposing a, "

这是 Tomas Petricek 的示例减去 do。括号为它们包含的代码块定义了一个新的范围,因此当您到达右括号时,b 超出范围并被释放。为了证明作用域规则是这样工作的,让我们添加另一个变量定义:

let aba() =
    use a = d "a"
    (
        use b = d "b"
        let c = 5
        printf "a + b, and c=%d " c  // This compiles
    )
    printf "and now c=%d " c  // This does not compile
    printf "a, "
aba(); printfn ""

括号内的 c 的第一次使用将编译。括号外的 c 的第二次使用不会编译,因为此时 c 超出范围,您将得到:

error FS0039: The value or constructor 'c' is not defined.

因此,如果您使用 do 表达式来控制 IDisposable 何时超出范围,则仅使用括号即可获得相同的结果。由您决定哪种形式更具可读性。