Using an example from Chris Smith's Programming F# 3.0:
let invalidUseOfMutable() =
let mutable x = 0
let incrementX() = x <- x + 1
incrementX()
x;;
This fails as expected:
error FS0407: The mutable variable 'x' is used in an invalid way. Mutable variables cannot be captured by closures.
Now cut and paste the body of the function into FSharp Interactive:
let mutable x = 0
let incrementX() = x <- x + 1
incrementX()
x;;
And it works!
val it : int = 1
Why?
Edit: the following answer is correct for F# up to 3.x. Starting with F# 4.0, local mutables are automatically converted into ref
s if needed, so OP's code will actually successfully compile in all cases.
Short answer: it's not because of fsi
, it's because the mutable is global.
Long answer:
For a normal (non-mutable) capture, implementation-wise the captured value is copied into the function object, so that if you return this function and use it outside of the scope in which it has been defined, everything works fine.
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
On the other hand, in order to capture a mutable, the capture needs to be done by reference, else you wouldn't be able to modify it. But this is impossible, because in the previously mentioned case where the closure is returned and used outside of its definition scope, the mutable is also out of scope and potentially deallocated. This is the reason for the initial limitation.
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.
However, if the mutable is global, then there is no such scope issue, and the compiler accepts it. It's not just fsi
; if you try to compile the following program with fsc
, it works:
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!
In conclusion, as kwingho said, if you want to have a closure that captures a local mutable value, use a ref
. They are heap-allocated (as opposed to the stack-allocated local mutable) so as long as the closure holds a reference to it, it won't be deallocated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With