OCaml中的不可变变量

时间:2016-02-26 01:00:01

标签: ocaml

我正在学习OCaml,我对变量的不变性感到困惑。根据我正在阅读的书,变量是不可改变的。到目前为止一切顺利,但为什么我可以这样做:

let foo = 42
let foo = 4242

我错过了什么?

3 个答案:

答案 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

以上代码执行以下操作:

  1. 42绑定到名称foo
  2. 创建一个函数return_foo (),返回绑定到foo的值。
  3. 24绑定到名称foo(隐藏foo的先前绑定)。
  4. 调用return_foo ()函数,该函数返回42
  5. 将此与可变值的行为(在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
    

    其中:

    1. 创建包含42的可变引用,并将其绑定到名称foo
    2. 创建一个函数return_foo (),它返回存储在绑定到foo的引用中的值。
    3. 24的参考文件中存储foo
    4. 调用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
    }
}

请参阅?这里没有对任何变量的赋值。