immutable in F#

2019-02-16 23:10发布

问题:

I know that variables in F# are immutable by default. But, for example in F# interactive:

  > let x = 4;;

val x : int = 4

> let x = 5;;

val x : int = 5

> x;;
val it : int = 5
> 

So, I assign 4 to x, then 5 to x and it's changing. Is it correct? Should it give some error or warning? Or I just don't understand how it works?

回答1:

When you write let x = 3, you are binding the identifier x to the value 3. If you do that a second time in the same scope, you are declaring a new identifier that hides the previous one since it has the same name.

Mutating a value in F# is done via the destructive update operator, <-. This will fail for immutable values, i.e.:

> let x = 3;;

val x : int = 3

> x <- 5;;

  x <- 5;;
  ^^^^^^

stdin(2,1): error FS0027: This value is not mutable

To declare a mutable variable, add mutable after let:

let mutable x = 5;;

val mutable x : int = 5

> x <- 6;;
val it : unit = ()
> x;;
val it : int = 6

But what's the difference between the two, you might ask? An example may be enough:

let i = 0;
while i < 10 do
    let i = i + 1
    ()

Despite the appearances, this is an infinite loop. The i declared inside the loop is a different i that hides the outer one. The outer one is immutable, so it always keeps its value 0 and the loop never ends. The correct way to write this is with a mutable variable:

let mutable i = 0;
while i < 10 do
    i <- i + 1
    ()


回答2:

x is not changed, it's just hidden by next declaration. For example:

> let x = 4;;
val x : int = 4
> let x = "abc";;
val x : string = "abc"
>


回答3:

You're not assigning 5 to x, you are defining a new variable.

The following example shows that there are two distinct variables. (It also shows that you can "access" the old x if it is in a closure, used by another function):

let x = 5;;
let f y = y+x;;
f 10;;
let x = 0;;
f 10;;

yields

>
val x : int = 5

>
val f : int -> int
> val it : int = 15
>
val x : int = 0

> val it : int = 15

as you see, both calls to f use the first variable x. The definition let x = 0;; defines a new variable x, but does not redefines f.



回答4:

Here's a minimal example illustrating identifier "shadowing" (i.e. hiding) in F#:

let x = 0
do //introduce a new lexical scope
    let x = 1 //"shadow" (i.e. hide) the previous definition of x
    printfn "%i" x //prints 1
//return to outer lexical scope
printfn "%i" x //prints 0, proving that our outer definition of x was not mutated by our inner definition of x

Your example is actually a bit more complex, because you are working in the F# Interactive (FSI). FSI dynamically emits code that looks something like the following in your example:

module FSI_0001 =
    let x = 4;;

open FSI_0001 //x = 4 is now available in the top level scope
module FSI_0002 =
    let x = 5;;

open FSI_0002 //x = 5 is now available in the top level scope, hiding x = 4
module FSI_0003 =
    let it = x;;

open FSI_0003
//... subsequent interactions