【问题标题】:Mutable reference cells don't work with static members可变参考单元格不适用于静态成员
【发布时间】:2014-11-24 18:25:59
【问题描述】:

我想我明白这里发生了什么,但我非常感谢有人能解释一下为什么会发生这种情况。

基本上(我在这里动态编码,以给出最简单的说明)我有一个类 S,除其他外,它维护一个符号表,将名称与类的对象相关联。所以,在我定义的类里面:

static member (names:Map<string,S> ref) = ref Map.empty

...然后,我也在 S 中定义:

member this.addSymbol (table: Map<string,S> ref) (name:string)
    table := (!table).Add(name, this)
    this

我从其他地方调用它,因此:

ignore (s.addSymbol S.names "newname")
...

在 S.addSymbol 内部,我可以看到新的键/值被添加到表中,但 S.names 没有得到更新。相反,如果我这样调用 addSymbol:

ignore (
    let tmp = S.names
    s.addSymbol tmp "newName"
)

然后我可以看到 tmp 正在使用新的键/值对进行更新,但 S.names 仍然没有得到更新。

以下是我认为我所知道的:ref 机制没有任何问题——它在函数内部和外部都正确地更新了所指对象。但是静态成员似乎有些奇怪 - 就像,它不是每次都给我相同的静态 ref 'a,而是复制 'a,然后为此创建一个新的 ref。

我做对了吗?如果是这样,它为什么要这样做?我该怎么做才能让它表现得更明智?

提前致谢。

【问题讨论】:

  • @Vandroiy 已经诊断出你的直接问题,但这里有一个额外的想法:如果你要改变一个集合,那么使用标准的 .NET 可变集合而不是引用通常更有意义包装您更新的不可变集合。在您的情况下,这意味着使用 System.Collections.Generic.Dictionary&lt;_,_&gt; 而不是 Map&lt;_,_&gt; ref。
  • 我确实考虑过使用 .net 集合,但最终决定不这样做,部分原因是架构上的原因(我有很多需要保持同步的表,所以我不想暴露添加方法),但也因为它感觉像是一种逃避——我刚刚开始使用 F#,并且我正在尝试以“正确”的方式做事,而不是仅仅使用 C# 类似的东西。我从指导中注意到,人们应该将对象用于大的东西,而将功能用于精细的东西。不知道我的问题是什么,但我想我是在表达一种缺乏知情判断的感觉:-)。

标签: f# ref


【解决方案1】:

静态成员names 是一个属性,而不是静态值。它在内部是一个每次调用时都会创建一个新地图的函数。

如果值必须在类中,请使用static let 在类中定义它。这样,参考单元格只会创建一次。

type S () =
    static let names = ref Map.empty : Map<string, S> ref
    static member Names = names

S.Names := ["Don", S()] |> Map.ofList
S.Names // gives map with one element

通常,该类不公开引用单元格本身,而是公开成员以读取、添加或删除名称。然后,Names 将返回 !names - 或者名称将是一个普通的可变而不是参考单元格 - 并且像 AddName 这样的其他方法会改变值。如果不需要这样的封装,请参阅 kvb 的评论,了解如何将此示例缩短为只有一个成员。 (感谢提示!)


替代方案: 通常,这是使用 F# 模块的情况。只需定义

let names = ref Map.empty : Map<string, S> ref

或

let mutable names = Map.empty : Map<string, S>

在一个模块中,根据需要具有额外的访问限制或封装。

【讨论】:

  • 你也可以使用“自动实现的属性”:static member val names : Map&lt;string, S&gt; ref = ref Map.empty
  • 谢谢,Vandroiy - 这是一个非常清楚的解释。在您的第一个建议中,它需要那个额外的名称,这是一种耻辱。我想我更喜欢第二个答案。但是 kvb 的回答可能是实现我想要做的最简洁的方法,而且我刚刚学到了很多关于 val 所做的事情,所以也应该感谢你。但是,我确实想到,给定的静态成员每次都会创建一个新对象,而 val 特别意味着该文件在构造时创建一次,未修饰的静态成员是一个非常无用的东西 - 除非我错过了什么?
  • @Jules 这是一个封装问题。通常,您会有私有值来保存原始数据结构,然后是允许对它们进行操作的成员。 Names 可以取消引用该值,因此它只读取地图,但不能更改它。然后,像AddName 这样的成员可以允许某些明确定义的操作。这取决于所需的安全需求或可变值周围的限制。 (我编辑了答案以指向 kvb 的评论并提及这一点。)
猜你喜欢
  • 2013-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-09
  • 2019-04-03
相关资源
最近更新 更多