let x = 42;;
val x : int = 42
Wait, how exactly are we defining x? We're essentially writing
let x = 42 in
(* the rest of what we type in the toplevel *)
let x = 7;;
val x : int = 7
We're not re-declaring x, we're just declaring another x on top of it! The equivalent of
let x = 42 in
let x = 7 in
(* everything else *)
This is called overlapping scope. It's confusing, and here's another example:
let x = 5 in (let x = 6 in x) + x;;
- : int = 11
It's a bit of a brain twister what this means, and while it does have a strict definition, it's annoying to think about. Let's avoid it in our programs.
Enter the principle of name irrelevance, a term invented by Prof. Clarkson. It says that the name of a variable shouldn't intrinsically matter.
E.g. in math, these are the same function:
They both add 1 to their argument.
To obey this rule, we need to stop substituting when we reach a binding of the same name.
let x = 5 in (let x = 6 in x);;
File "[4]", line 1, characters 4-5:
1 | let x = 5 in (let x = 6 in x);;
^
Warning 26: unused variable x.
- : int = 6
See that unused variable warning? That's because we just dropped the value 5 for x when we reached the next let binding.