F#改变由对象组成的状态

时间:2012-10-01 03:54:34

标签: f# pass-by-reference immutability record mutable

我有一个模拟器,它将一些状态保存为具有两个成员的记录:Class1的一个对象和Class2的一系列对象。

当模拟运行时,读取输入,并根据输入调用这些对象的某些方法。这种方法改变了他们的内部状态。

我正在学习F#,我理解不可变数据的重要性。但是,考虑到这种状态的复杂性(远远超过这里暴露),我认为拥有内部可变状态的这些对象并不是什么大问题。至少它是隐藏的。

然而,问题是另一个问题。它可能很简单。

在迭代之间,我失去了对“一个”和“多个”对象的更改!

我猜InvokeMethodOn(显然是简化的)会获取这些对象的副本。

我意识到我需要某种参考,但是......我在这里有点迷失......国家应该有参与成员吗? InvokeMethodOn应该通过ref传递?他们都是?那么“很多”的序列呢?

编辑:可能有数百万“很多”对象。它们每个都有1或2 KB的状态(现在只保留在一个字节的blob中)。

编辑:将“many”更改为数组(并按建议使用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

2 个答案:

答案 0 :(得分:2)

  

在迭代之间,我失去了对“一个”和“多个”对象的更改!

     

我猜InvokeMethodOn(显然是简化的)会获取这些对象的副本。

你猜错了; InvokeMethodOn仅修改Class1Class2的当前状态。假设您每次迭代都有State条记录。因为您没有在任何地方创建新的Class1Class2实例,所以这些记录都指向相同的类实例,并且在每次迭代中都以相同的方式进行修改。

  

我认为拥有内部可变状态的对象并不是什么大问题。至少它是隐藏的。

这是一个大问题。您的隐藏状态被泄露并导致错误行为。我相信你担心性能,所以你想改变Class1Class2的状态。我不知道参考传递如何帮助你。一种简单的解决方法是编写

member x.InvokeMethodOn obj input =
         obj.CreateNewInstanceWith input

并通过在字段上调用while,将Seq.fold更改为某种State,其中您返回新的InvokeMethodOn

我认为如果您将Class1Class2声明为记录并使用with阻止{class1 with value = newValue}会更好。如果需要进行性能优化,您总是可以在以后更改记录以获得可变字段。此外,不要将seq声明为记录字段,它会破坏记录中的结构相等

答案 1 :(得分: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的此页面 它没什么吸引力,应该澄清很多东西。

关于可变数据还有一点需要注意的是,默认情况下,数组是可变的:不需要重新声明它们是可变的。