2012-10-01 63 views
1

我有一個模擬器,它將一些狀態保存爲兩個成員的記錄:一個Class1對象和一個Class2對象序列。F#改變對象的狀態

當仿真運行時,讀取輸入,並根據輸入,調用這些對象的某些方法。這種方法改變他們的內部狀態。

我正在學習F#,我理解不可變數據的重要性。然而,考慮到這種狀態的複雜性(在此暴露的遠不止這些),我認爲讓這些內部可變狀態的對象不是什麼大問題。至少它是隱藏的。

然而,問題在於另一個問題。這可能很簡單。

在迭代之間,我失去了對「一個」和「多個」對象的更改!

我想InvokeMethodOn(顯然簡化)需要這些對象的副本。

我知道我在這裏需要某種引用,但是......我在這裏有點失落......國家應該有裁判成員? InvokeMethodOn應該通過ref?他們全部?那麼「很多」序列呢?

編輯:可能有數以百萬計的「許多」對象。它們中的每一個都有1或2 KB的狀態(現在只保留在一個字節中)。

編輯:更改「許多」爲一個數組(並按照建議使用Array.iter)解決了這個問題。感謝大家!

type State = { 
    one : Class1 
    many : Class2 seq 
} 

type Simulator() = class 
    member x.run(state : State) = 
     // .... 
     while ... 
      let input = ReadInput 
      if someFuncOf(input) 
       then InvokeMethodOn(state.one, input) 
       else Seq.iter (fun x -> InvokeMethodOn(x, input)) state.many     

    member x.InvokeMethodOn obj input = 
     obj.ChangeInternalState input 
+0

你的'ChangeInternalState'可能有問題,'seq'因爲'seqs'懶惰需要小心 - 可能最好使用數組/列表 –

+0

'obj.ChangeInternalState'只改變是否正確例如'Class2'內的東西'Class2.mutableValue'?或者你的目標是改變'State.many',使它指向不同的'Class2'對象序列? – bytebuster

+0

第一個假設是正確的:obj.ChangeInternalState只更改內部屬性。在代碼中,我沒有改變「一」或「多」。 – Benedetto

回答

2

如果您的Class1和Class2包含您更改的可變狀態,我不會看到在每次迭代中這些更改都會被丟棄的原因,除非您以某種方式重新創建Class1的新副本。

如果我嘗試編寫類似的東西而不是呈現的東西,它會運行罰款。 知道您的代碼如何與此分離以找到我們錯過的地方會很有趣。

type Class1 = { mutable label : string} 
type Container = { one : Class1; many : Class1 seq} 

let a = { label = "a" } 
let bs = [ { label = "b1" } ; { label = "b2" }] 

let cont = { one =a ; many = bs} 
printfn "%A" cont.one.label 

cont.one.label <- "changed a" 
cont.many |> Seq.iter (fun x -> x.label <- "changed b") 
printfn "%A" cont 

cont.one.label <- "changed again a" 
printfn "%A" cont 

注意,在F#REF是真正的「可變內容」只是一個隱藏的表示

type 'a ref = { mutable contents : 'a } 

您可能需要閱讀此頁有關mutation in FSharp 有小魔術給它,它應該澄清很多東西。

另外需要注意的是,有關可變數據的一點是該數組默認是可變的:不需要重新聲明它們是可變的。

+0

我用我的代碼看到的唯一區別是,您直接更改「one」和「many」中的可變字段,同時我調用這些對象中的方法來更改其內部可變字段。 – Benedetto

2

在迭代之間我失去更改爲「一」與「多」的對象!

我想InvokeMethodOn(顯然簡化)需要這些對象的副本。

你猜錯了; InvokeMethodOn僅修改當前狀態Class1Class2。假設您每次迭代都有一個State記錄。因爲您沒有在任何地方創建新的實例Class1Class2,所以這些記錄都指向相同的類實例,並且在每次迭代中都以相同的方式進行修改。

我認爲擁有這種內部可變狀態的對象並不是什麼大不了的事。至少它是隱藏的。

這是一件大事。您的隱藏狀態泄漏並導致錯誤行爲。我相信你擔心表現會讓你想要改變Class1Class2的狀態。我不知道如何通過引用可以幫助你。一個簡單的方法來解決是寫

member x.InvokeMethodOn obj input = 
     obj.CreateNewInstanceWith input 

,改變while以某種Seq.fold,你通過調用場返回InvokeMethodOnState

我認爲這會更好,如果你宣佈Class1Class2作爲記錄和使用with塊:{class1 with value = newValue}。如果您需要進行性能優化,您總是可以稍後將記錄更改爲具有可變字段。此外,不要聲明seq作爲記錄字段,它會破壞記錄上的結構性等於

+0

你猜對了,我擔心表現。有數百萬個「許多」對象,每個都攜帶幾KB的狀態。大多數情況下,我需要修改其狀態的幾個字節。 我認爲這可能是最好的路徑。 – Benedetto

+0

正如我在回答中所說的,你應該首先確保正確性。從一個不可變的版本開始,以便您輕鬆推理。從正確的版本優化性能會容易得多。 – pad