OCaml 中的不可变变量
Immutable variables in OCaml
我正在学习 OCaml,我对变量的不变性有点困惑。根据我正在阅读的书,变量是不可变的。到目前为止一切顺利,但我到底为什么可以这样做:
let foo = 42
let foo = 4242
我错过了什么??
名称 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 仍然绑定到相同的值,但该值包含不同的数据。
我希望我没有让你更加困惑 ;)
我认为最好的解释方式是举个例子。考虑这段代码(在 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 ()
,returns 值绑定到 foo
。
- 将
24
绑定到名称 foo
(这隐藏了之前 foo
的绑定)。
- 调用
return_foo ()
函数,其中 returns 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 ()
,该函数 returns 存储在绑定到 foo
的引用中的值。
- 将
24
存储在绑定到 foo
的引用中。
- 调用
return_foo ()
函数,returns 24
.
let
的通常形式是let ... in
表达式,在这里你定义了一个变量绑定,它只存在于let
的主体内部。 let
的正文是一个新范围。
let x = 42 in (* body here *)
这里的let
的"body"是一个新的作用域,和外面的不同,所有的外部变量都多了一个局部变量x
也就是仅在此 let
.
的正文中定义
现在您谈论的是文件顶层的 let
s。这些在语法上看起来有点不同(没有 in
),但实际上它们是相同的,"body" 是文件的其余部分。所以在这里你可以把 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
}
}
看到了吗?这里没有给任何变量赋值。
我正在学习 OCaml,我对变量的不变性有点困惑。根据我正在阅读的书,变量是不可变的。到目前为止一切顺利,但我到底为什么可以这样做:
let foo = 42
let foo = 4242
我错过了什么??
名称 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 仍然绑定到相同的值,但该值包含不同的数据。
我希望我没有让你更加困惑 ;)
我认为最好的解释方式是举个例子。考虑这段代码(在 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 ()
,returns 值绑定到foo
。 - 将
24
绑定到名称foo
(这隐藏了之前foo
的绑定)。 - 调用
return_foo ()
函数,其中 returns42
.
将此与可变值的行为(在 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 ()
,该函数 returns 存储在绑定到foo
的引用中的值。 - 将
24
存储在绑定到foo
的引用中。 - 调用
return_foo ()
函数,returns24
.
let
的通常形式是let ... in
表达式,在这里你定义了一个变量绑定,它只存在于let
的主体内部。 let
的正文是一个新范围。
let x = 42 in (* body here *)
这里的let
的"body"是一个新的作用域,和外面的不同,所有的外部变量都多了一个局部变量x
也就是仅在此 let
.
现在您谈论的是文件顶层的 let
s。这些在语法上看起来有点不同(没有 in
),但实际上它们是相同的,"body" 是文件的其余部分。所以在这里你可以把 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
}
}
看到了吗?这里没有给任何变量赋值。