Warm tip: This article is reproduced from serverfault.com, please click

其他-在F#中不可变的可变字典

(其他 - mutable dictionary immutable in F#)

发布于 2020-11-29 10:24:35

我有声明可变字典的代码,但是当我尝试更改元素时会出现错误。

代码:

   let layers =
        seq {
            if recipes.ContainsKey(PositionSide.Short) then yield! buildLayerSide recipes.[PositionSide.Short]
            if recipes.ContainsKey(PositionSide.Long)  then yield! buildLayerSide recipes.[PositionSide.Long]
        }
        |> Seq.map (fun l -> l.Id, l)
        |> dict

这将创建一个IDictionary我知道对象本身是不可变的,但是字典的内容应该是可变的。

当我通过显式初始化字典来更改代码时,它变得可变了:

   let layers =
        let a =
            seq {
                if recipes.ContainsKey(PositionSide.Short) then yield! buildLayerSide recipes.[PositionSide.Short]
                if recipes.ContainsKey(PositionSide.Long)  then yield! buildLayerSide recipes.[PositionSide.Long]
            }
            |> Seq.map (fun l -> l.Id, l)
            |> dict
    let x = Dictionary<string, Layer>()
    a
    |> Seq.iter (fun kvp -> x.[kvp.Key] <- kvp.Value)

    x

这是为什么?

Questioner
Thomas
Viewed
11
Fyodor Soikin 2020-11-30 01:11:42

IDictionary是一个接口,而不是一个类。该接口可以具有多种不同的实现。你甚至可以自己做一个。

Dictionary确实是这些实现之一。它支持界面的全部功能。

但这不是dict函数返回的实现让我们尝试一下:

> let d = dict [(1,2)]
> d.GetType().FullName
"Microsoft.FSharp.Core.ExtraTopLevelOperators+DictImpl`3[...

事实证明,该dict函数返回的实现Microsoft.FSharp.Core.ExtraTopLevelOperators.DictImpl-DictImpl在F#标准库的内部深入定义的名为class的类

碰巧的是,该接口上的某些方法抛出了NotSupportedException

> d.Add(4,5)
System.NotSupportedException: This value cannot be mutated

那是设计使然。故意这样做是为了支持“默认情况下的不变性”。

如果你确实想拥有一个可变的版本,则可以使用Dictionary的构造函数之一来创建一个副本

> let m = Dictionary(d)
> m.Add(4,5)  // Works now

Map之间的区别Dictionary是实现,这意味着内存和运行时特性。

Dictionary是一个哈希表。它提供了固定时间的插入和检索,但是要付出代价,它依赖于其键的一致散列,并且其更新是破坏性的,这也带来了线程不安全的问题。

Map被实现为一棵树。它提供对数插入和检索,但作为回报,它具有持久数据结构的优点此外,它要求密钥具有可比性。尝试这个:

> type Foo() = class end
> let m = Map [(Foo(), "bar")]
error FS0001: The type 'Foo' does not support the 'comparison' constraint

比较键对于构建树是必不可少的。