Reputation: 24892
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??
Upvotes: 5
Views: 1990
Reputation: 122429
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.
Upvotes: 3
Reputation: 1151
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:
42
to the name foo
.return_foo ()
that returns the value bound to foo
.24
to the name foo
(which hides the previous binding of foo
).return_foo ()
function, which returns 42
.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:
42
and binds it to the name foo
.return_foo ()
that returns the value stored in the reference bound to foo
.24
in the reference bound to foo
.return_foo ()
function, which returns 24
.Upvotes: 10
Reputation: 35210
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 ;)
Upvotes: 4