9

使用 Chris Smith 的Programming F# 3.0中的示例:

let invalidUseOfMutable() =
    let mutable x = 0
    let incrementX() = x <- x + 1
    incrementX()
    x;;

这如预期的那样失败:

错误 FS0407:可变变量“x”的使用方式无效。可变变量不能被闭包捕获。

现在将函数的主体剪切并粘贴到 FSharp Interactive 中:

let mutable x = 0
let incrementX() = x <- x + 1
incrementX()
x;;

它有效!

验证它:int = 1

为什么?

4

1 回答 1

10

编辑:以下答案对于 F# 到 3.x 是正确的。从 F# 4.0 开始,如果需要,本地可变变量会自动转换为refs,因此 OP 的代码实际上在所有情况下都会成功编译。


简短的回答:这不是因为fsi,而是因为可变是全局的。

长答案:

对于普通(非可变)捕获,在实现方面将捕获的值复制到函数对象中,因此如果您返回此函数并在定义它的范围之外使用它,一切正常。

let pureAddOne() =
    let x = 1
    let f y = x + y    // the value 1 is copied into the function object
    f

let g = pureAddOne()
g 3    // x is now out of scope, but its value has been copied and can be used

另一方面,为了捕获一个可变的,捕获需要通过引用来完成,否则你将无法修改它。但这是不可能的,因为在前面提到的闭包被返回并在其定义范围之外使用的情况下,可变对象也超出了范围并可能被释放。这就是初始限制的原因。

let mutableAddOne() =
    let mutable x = 1
    let f y = x <- x + y    // x would be referenced, not copied
    f

let g = mutableAddOne()
g 3    // x is now out of scope, so the reference is invalid!
       // mutableAddOne doesn't compile, because if it did, then this would fail.

但是,如果 mutable 是全局的,则不存在这样的范围问题,并且编译器会接受它。不只是fsi;如果您尝试使用 编译以下程序fsc,它可以工作:

module Working

let mutable x = 1    // x is global, so it never goes out of scope

let mutableAddOne() =
    let f y = x <- x + y    // referencing a global. No problem!
    f

let g = mutableAddOne()
g 3    // works as expected!

总之,正如 kwingho 所说,如果您想要一个捕获本地可变值的闭包,请使用ref. 它们是堆分配的(与堆栈分配的本地可变变量相反),因此只要闭包持有对它的引用,它就不会被释放。

于 2013-05-12T21:41:02.523 回答