我正在学习OCaml,我对变量的不变性感到困惑。根据我正在阅读的书,变量是不可改变的。到目前为止一切顺利,但为什么我可以这样做:
let foo = 42
let foo = 4242
我错过了什么?
答案 0 :(得分:9)
我认为解释的最好方法是举个例子。考虑这段代码(在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
以上代码执行以下操作:
42
绑定到名称foo
。return_foo ()
,返回绑定到foo
的值。24
绑定到名称foo
(隐藏foo
的先前绑定)。return_foo ()
函数,该函数返回42
。将此与可变值的行为(在OCaml中使用ref
创建)进行比较:
# 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
其中:
42
的可变引用,并将其绑定到名称foo
。return_foo ()
,它返回存储在绑定到foo
的引用中的值。24
的参考文件中存储foo
。return_foo ()
函数,该函数返回24
。答案 1 :(得分:4)
名称foo
首先绑定到不可变值42
,然后它会反弹到另一个不可变值4242
。您甚至可以将相同的名称绑定到不同类型的变量。在OCaml中,我们谈论的不是变量的可变性,而是关于值的可变性。例如,如果将foo
绑定到值数组,则它将是相同的名称,但绑定到可变数据,以便变量的值可以及时更改。最后,每个新绑定只隐藏前一个绑定,因此原始foo仍然绑定到42
,但它是不可见的并且将被垃圾收集。
也许一个小例子可以澄清这个想法:
let example () =
let foo = 42 in (* 1 *)
let foo = 4242 in (* 2 *)
let foo = [|42|] in (* 3 *)
foo.(0) <- 56 (* 4 *)
拥有以下心理模型可能更容易:
(*1*) +--------+
+----> | 42 |
+------------+ | +--------+
| +----+
| foo +----+ +--------+
| | +----> | 4242 |
+---------+--+ (*2*) +--------+
|
| (*3*) +--------+
+------------> |[| 42 |]|
(*4*) +--------+
在1
行和2
行,我们只需将变量foo
绑定到两个不同的值。在行3
上,我们将它绑定到包含一个元素的数组。在行4
上,我们更改了值,并且foo仍然绑定到相同的值,但该值包含不同的数据。
我希望我不要再混淆你了;)
答案 2 :(得分:2)
let
的通常形式是let ... in
表达式,您可以在其中定义变量绑定,该绑定仅存在于let
的主体内部。 let
的正文是一个新范围。
let x = 42 in (* body here *)
这里&#34;身体&#34; let
的新范围与外部范围不同,所有来自外部的变量都有一个额外的局部变量x
,仅在此let
的正文中定义
现在您正在讨论文件顶层的let
。这些在语法上看起来有点不同(没有in
),但实际上它们是相同的,使用&#34; body&#34;作为文件的其余部分。因此,您可以在let
之后将文件的其余部分视为新范围,x
是此范围的局部变量。所以你的代码等同于:
let foo = 42 in (
let foo = 4242 in (
(* rest of file *)
)
)
此处,您的内部let
绑定一个局部变量,该变量与外部作用域中已存在的变量同名。这没关系。您正在内部范围中绑定新变量。如果它恰好与外部作用域中的变量具有相同的名称,则引用该名称的内部作用域中的代码将引用最内层的绑定。但是,这两个变量是完全独立的。
在类似C语言中,它会是这样的:
{
const int foo = 42;
{
const int foo = 4242;
// rest of code here
}
}
请参阅?这里没有对任何变量的赋值。