2016-02-26 74 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 
    } 
} 

看到了嗎?這裏沒有賦值給任何變量。