2015-06-13 65 views
3

當我在F#中創建一個包含F#中的泛型,可變.NET堆棧的類時,該堆棧忽略了我所推送的任何東西。F#中member val和member之間的區別是什麼?

open System.Collections.Generic 

type Interp(code: int array) = 
    member val PC = 0 with get, set 
    member this.stack: Stack<int> = new Stack<int>() 
    member this.Code = code 

let interp = Interp([|1;2;3|]) 
interp.stack.Push(1) 
printfn "%A" interp.stack // prints "seq[]" WAT?! 

然而,如果我做堆棧可變通過屬性:

open System.Collections.Generic 

type Interp(code: int array) = 
    member val PC = 0 with get, set 
    member val stack: Stack<int> = new Stack<int>() with get, set 
    member this.Code = code 

let interp = Interp([|1;2;3|]) 
interp.stack.Push(1) 
printfn "%A" interp.stack // prints "seq[1]" 

一切神奇就像我所期待。

這到底是怎麼回事?我對先前語言(主要是C#)的不變性的理解會說,即使第一個例子中的堆棧是不可變的成員,但這個不可變性只應該引用到參考(也就是說我不應該重新分配堆棧本身)。我仍然應該能夠向它推送值。我錯過了什麼,如果試圖改變堆棧是錯誤的,爲什麼不拋出異常或編譯錯誤?

回答

5

如果您嘗試編譯第一個版本,然後使用例如反射器將其編譯到C#,你會看到堆疊成員被這樣定義:

public class Interp 
{ 
    public Stack<int> stack 
    { 
     get { return new Stack<int>(); } 
    } 

    // Other members omitted for clarity... 
} 

正如你所看到的,這也是有效的C#代碼,但顯然不是你想要的。

第二個版本的交叉編譯的東西是這樣的:

public class Interp 
{ 
    internal int[] code; 
    internal Stack<int> [email protected]; 

    public Interp(int[] code) : this() 
    { 
     this.code = code; 
     [email protected] = new Stack<int>(); 
    } 

    public Stack<int> stack 
    { 
     get { return [email protected]; } 
     set { [email protected] = value; } 
    } 

    // Other members omitted for clarity... 
} 

這似乎更像是你想要的屬性做什麼。

2

更慣用的方式做你想要的是這樣的:

open System.Collections.Generic 

type Interp(code: int array) = 
    let stack = Stack<int>() 
    member val PC = 0 with get, set 
    member this.Stack = stack 
    member this.Code = code 

如果不需要外部暴露棧,忽略旁邊的最後一行。

+0

關於'member val Stack = new Stack (),get,set',什麼是不成語? – Daniel

+0

@Daniel爲不會變異的成員創建一個可變字段不是慣用的。 – phoog

+0

@Daniel或更多點,通常更喜歡不可變類型,或者至少會員,而不是可變類型。 – phoog

相關問題