繁体   English   中英

可变字典在 F# 中不可变

[英]mutable dictionary immutable in F#

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

编码:

   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 我知道 object 本身是不可变的,但字典的内容应该是可变的。

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

   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

这是为什么?

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

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

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

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

结果发现dict function 返回的实现是Microsoft.FSharp.Core.ExtraTopLevelOperators.DictImpl - 一个名为DictImpl的 class 在 Z629B160731F353947EAC 标准库的内部深处定义

碰巧该接口上的某些方法会抛出NotSupportedException

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

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

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

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

MapDictionary之间的区别在于实现,这意味着 memory 和运行时特性。

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

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

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

比较键对于构建树至关重要。

不同之处在于dict是一个只读字典,其中包含一些引发异常的变异方法(这是该类型的一个缺点),而 map 是一个不可变集合,它使用Map模块中的函数以及修改元素的方法map 并返回一份副本。 Get Programming with F#一书的第 17 课对此有很好的解释。

此外,对于dict ,“使用其键检索值非常快,接近 O(1),因为 Dictionary class 是作为 hash 表实现的。” 来自文档 map基于二叉树,因此使用其键检索值具有 O(log(N)) 复杂度。 请参阅集合类型 这也意味着map中的键是有序的; 而在dict中,它们是无序的。

对于许多用例,性能差异可以忽略不计,因此函数式编程风格的默认选择应该是map ,因为它的编程接口在风格上与其他list seq类似。

dict是创建iDictionary的辅助方法,该字典是不可变的(因此您需要在创建对象期间提供内容)。 它实际上是一个只读字典,所以你不能修改它的内容也就不足为奇了。 在您的第二个示例中,您显式创建了一个Dictionary ,它是一个可变字典。 由于 Dictionary 可以使用 iDictionary,因此您只需将 iDictionary 传递给它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM