为什么F#交互式允许闭包捕获可变变量?

10

使用 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;;

它有效!

val it : int = 1

为什么?


2
嗨,当你只运行主体代码时,它不会生成闭包。使用: let x = ref 0; let incrementX() = x:=!x + 1;请参阅闭包:http://msdn.microsoft.com/en-us/library/dd233186.aspx - kwingho
请注意,编译器本身也不会对函数体本身提出异议。 - John Palmer
1个回答

11

编辑:以下答案适用于F# 3.x及以下版本。从F# 4.0开始,如果需要,本地可变量会自动转换为ref,因此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.

然而,如果可变对象是全局的,那么就不存在这样的作用域问题,编译器会接受它。这不仅适用于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。它们是堆分配的(而不是栈分配的本地可变),因此只要闭包持有对它的引用,它就不会被释放。


1
一些对我来说重要的点:使用 ref 来捕获本地可变变量,F#(从4.0开始)会自动将 mutable 转换为 ref。因此,代码在 F# 4.0 中始终有效。 - Ta Thanh Dinh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接