使用Chris Smith的Programming F#3.0中的示例:
let invalidUseOfMutable() =
let mutable x = 0
let incrementX() = x <- x + 1
incrementX()
x;;
这将按预期失败:
error FS0407: The mutable variable 'x' is used in an invalid way. Mutable variables cannot be captured by closures.
现在,将函数的主体剪切并粘贴到FSharp Interactive中:
let mutable x = 0
let incrementX() = x <- x + 1
incrementX()
x;;
而且有效!
val it : int = 1
为什么?
最佳答案
编辑:以下答案对于3.x以下的F#是正确的。从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
。它们是堆分配的(与堆栈分配的本地可变对象相反),只要闭包持有对它的引用,就不会将其释放。
关于f# - 为什么Fsharp Interactive允许可变变量被闭包捕获?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16509587/