1

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

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

我正在学习 F#,我了解不可变数据的重要性。然而,考虑到这种状态的复杂性(远不止这里公开的),我认为拥有这些具有内部可变状态的对象并不是什么大不了的事。至少它是隐藏的。

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

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

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

我意识到我需要一些参考,但是......我在这里有点迷失......状态应该有参考成员吗?InvokeMethodOn 应该通过 ref 传递吗?他们都是?那么“多”序列呢?

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

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

2 回答 2

2

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

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

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

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

这是一件大事。您的隐藏状态被泄露并导致错误行为。我相信您正在担心性能,因此您想改变Class1andClass2的状态。我不知道通过引用传递如何帮助你。一个简单的修复方法是编写

member x.InvokeMethodOn obj input =
         obj.CreateNewInstanceWith input

并通过调用字段更改为您返回新的while某种位置。Seq.foldStateInvokeMethodOn

我认为如果您声明Class1andClass2作为记录并使用withblock:会更好{class1 with value = newValue}。如果您需要进行性能优化,您可以稍后将记录更改为具有可变字段。此外,不要声明seq为记录字段,它会破坏记录的结构平等

于 2012-10-01T06:20:57.303 回答
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 }

你可能想阅读这个关于FSharp 中的突变的页面 它没有什么魔力,它应该澄清很多事情。

关于可变数据需要注意的一件事是数组默认是可变的:不需要重新声明它们是可变的。

于 2012-10-01T08:28:39.677 回答