I have code that declares a mutable dictionary but I will get an error when I try to change an element.
The code:
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
this creates an IDictionary
. I understand that the object itself is immutable but the contents of the dictionary should be mutable.
When I change the code by explicitly initializing the dictionary then it becomes mutable:
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
Why is that?
IDictionary
is an interface, not a class. This interface may have multiple different implementations. You can even make one yourself.
Dictionary
is indeed one of these implementations. It supports the full functionality of the interface.
But that's not the implementation that the dict
function returns. Let's try this:
> let d = dict [(1,2)]
> d.GetType().FullName
"Microsoft.FSharp.Core.ExtraTopLevelOperators+DictImpl`3[...
Turns out the implementation that the dict
function returns is Microsoft.FSharp.Core.ExtraTopLevelOperators.DictImpl
- a class named DictImpl
defined deep in the innards of the F# standard library.
And it just so happens that certain methods on that interface throw a NotSupportedException
:
> d.Add(4,5)
System.NotSupportedException: This value cannot be mutated
That's by design. It's done this way on purpose, to support "immutability by default".
If you really want to have a mutable version, you can create a copy by using one of Dictionary
's constructors:
> let m = Dictionary(d)
> m.Add(4,5) // Works now
The difference between Map
and Dictionary
is the implementation, which then implies memory and runtime characteristics.
Dictionary
is a hashtable. It offers constant-time insertion and retrieval, but to pay for that it relies on consistent hashing of its keys and its updates are destructive, which also comes with thread-unsafety.
Map
is implemented as a tree. It offers logarithmic insertion and retrieval, but in return has the benefits of a persistent data structure. Also, it requires keys to be comparable. Try this:
> type Foo() = class end
> let m = Map [(Foo(), "bar")]
error FS0001: The type 'Foo' does not support the 'comparison' constraint
Comparing keys is essential for building a tree.
that clarifies it all; this was unclear for very long. So F# has two immutable dictionaries (Dictionary and Map) and the mutable BCL one.. also called Dictionary, but a totally different one. That opens an additional question: why can I do: xx |> dict but not xx |> Dictionary, while I can do xx |> Map, but xx |> map doesn't exist? also, does "Dictionary" change meaning (F# vs. BCL) when I open System.Collections.Generic since the name is the same?
You're probably confusing
Dictionary
andIDictionary
I understand they all fall under the IDictionary interface, but if I instantiate a Dictionary, wouldn't the keywork Dictionary point to the BCL type, instead of the F# type, if I include System.Collections.Generic?
It's not a keyword, it's an identifier, but yes, it does identify a BCL class. The F# standard library, however, does not expose such identifier.