问题
I'm learning OCaml, and I'm a bit confused with the immutability of variables. According to the book I'm reading, variables are immutable. So far so good, but why on Earth can I do this:
let foo = 42
let foo = 4242
What am I missing??
回答1:
I think the best way to explain is with an example. Consider this code (executed in the OCaml REPL):
# let foo = 42;;
val foo : int = 42
# let return_foo () = foo;;
val return_foo : unit -> int = <fun>
# let foo = 24;;
val foo : int = 24
# return_foo ();;
- : int = 42
The above code does the following:
- Binds
42
to the namefoo
. - Creates a function
return_foo ()
that returns the value bound tofoo
. - Binds
24
to the namefoo
(which hides the previous binding offoo
). - Calls the
return_foo ()
function, which returns42
.
Compare this with the behaviour of a mutable value (created using ref
in OCaml):
# let foo = ref 42;;
val foo : int ref = {contents = 42}
# let return_foo () = !foo;;
val return_foo : unit -> int = <fun>
# foo := 24;;
- : unit = ()
# return_foo ();;
- : int = 24
which:
- Creates a mutable reference containing
42
and binds it to the namefoo
. - Creates a function
return_foo ()
that returns the value stored in the reference bound tofoo
. - Stores
24
in the reference bound tofoo
. - Calls the
return_foo ()
function, which returns24
.
回答2:
The name foo
is first bound to an immutable value 42
and later it is rebound to another immutable value 4242
. You can even bind the same name to variables of different types. In OCaml we are talking not about mutability of a variable, but about a mutability of a value. For example, if you bind foo
to an array of values, this would be the same name, but bound to a mutable data, so that the value of a variable can change in time. Finally, each new binding just hides the previous one, so the original foo is still bound to 42
, but it is invisible and will garbage collected.
Maybe a little example will clarify the idea:
let example () =
let foo = 42 in (* 1 *)
let foo = 4242 in (* 2 *)
let foo = [|42|] in (* 3 *)
foo.(0) <- 56 (* 4 *)
It might be easier to have the following mental model:
(*1*) +--------+
+----> | 42 |
+------------+ | +--------+
| +----+
| foo +----+ +--------+
| | +----> | 4242 |
+---------+--+ (*2*) +--------+
|
| (*3*) +--------+
+------------> |[| 42 |]|
(*4*) +--------+
On lines 1
and 2
we just bind a variable foo
to two different values. On line 3
we bind it to an array that contains one element. On line 4
, we change the value, and foo is still bound to the same value, but the value contains different datum.
I hope I didn't confuse you even more ;)
回答3:
The usual form of let
is the let ... in
expression, where you define a variable binding, which only exists in the inside of the body of the let
. The body of the let
is a new scope.
let x = 42 in (* body here *)
Here the "body" of the let
is a new scope that is different from the one outside, with all the variables from the outside with an additional local variable x
that is only defined in the body of this let
.
Now you are talking about let
s at the top level of the file. These look a little different syntactically (there is no in
), but really they are the same, with the "body" being the rest of the file. So here you can think of the rest of the file after the let
as a new scope, with x
being a local variable of this scope. So your code is equivalent to this:
let foo = 42 in (
let foo = 4242 in (
(* rest of file *)
)
)
Here your inner let
binds a local variable that has the same name as a variable that already exists in the outer scope. That doesn't matter. You are binding a new variable in an inner scope. If it happens to have the same name as a variable in an outer scope, then code in the inner scope referencing that name will refer to the innermost binding. The two variables, however, are completely independent.
In a C-like language, it would be something like this:
{
const int foo = 42;
{
const int foo = 4242;
// rest of code here
}
}
See? There is no assignment to any variables here.
来源:https://stackoverflow.com/questions/35641464/immutable-variables-in-ocaml