可变字典在 F# 中不可变

Tho*_*mas 5 f# dictionary

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

编码:

   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
Run Code Online (Sandbox Code Playgroud)

这将创建一个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
Run Code Online (Sandbox Code Playgroud)

这是为什么?

Fyo*_*kin 5

IDictionary是一个接口,而不是一个类。这个接口可能有多种不同的实现。你甚至可以自己制作一个。

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

但这不是dict函数返回的实现。让我们试试这个:

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

事实证明,该dict函数返回的实现是Microsoft.FSharp.Core.ExtraTopLevelOperators.DictImpl——一个DictImpl在 F# 标准库内部定义的类。

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

> d.Add(4,5)
System.NotSupportedException: This value cannot be mutated
Run Code Online (Sandbox Code Playgroud)

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

如果你真的想要一个可变版本,你可以使用Dictionary的构造函数之一创建一个副本:

> let m = Dictionary(d)
> m.Add(4,5)  // Works now
Run Code Online (Sandbox Code Playgroud)

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

Dictionary是一个哈希表。它提供恒定时间的插入和检索,但要为此付出代价,它依赖于其密钥的一致散列,其更新是破坏性的,这也伴随着线程不安全。

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

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

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