2016-02-26 71 views
4

我正在学习OCaml,而且我对变量的不变性有些困惑。根据我正在阅读的书,变量是不可变的。到目前为止这么好,但为什么我可以这样做:OCaml中的不可变变量

let foo = 42 
let foo = 4242 

我在想什么?

+4

你不改变前一个的价值,只是作出一个新名字shadow shadowing前一个 –

回答

6

我想解释的最好方式是用一个例子。考虑下面的代码(在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

比较这与可变的值(创建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
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*) +--------+ 

上线12我们只是绑定变量foo到两个不同的值。在线3我们将它绑定到包含一个元素的数组。在行4上,我们更改了值,并且foo仍然绑定到相同的值,但是该值包含不同的数据。

我希望我没有混淆你更多;)

+0

非常感谢你! – cfischer

2

let通常的形式是let ... in表达,其中定义一个变量绑定,这仅在let的主体的内部存在。 let的主体是一个新的范围。

let x = 42 in (* body here *) 

这里的let的“身体”是一个新的范围是从外部的一个不同,从外部的所有变量与另外的局部变量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 
    } 
} 

看到了吗?这里没有赋值给任何变量。