The Google yields plenty of example of adding and deleting entries in an F# dictionary (or other collection). But I don't see examples to the equivalent of
myDict["Key"] = MyValue;
I've tried
myDict.["Key"] <- MyValue
I have also attempted to declare the Dictionary as
Dictionary<string, mutable string>
as well several variants on this. However, I haven't hit on the correct combination yet... if it is actually possible in F#.
Edit: The offending code is:
type Config(?fileName : string) =
let fileName = defaultArg fileName @"C:\path\myConfigs.ini"
static let settings =
dict[ "Setting1", "1";
"Setting2", "2";
"Debug", "0";
"State", "Disarray";]
let settingRegex = new Regex(@"\s*(?<key>([^;#=]*[^;#= ]))\s*=\s*(?<value>([^;#]*[^;# ]))")
do File.ReadAllLines(fileName)
|> Seq.map(fun line -> settingRegex.Match(line))
|> Seq.filter(fun mtch -> mtch.Success)
|> Seq.iter(fun mtch -> settings.[mtch.Groups.Item("key").Value] <- mtch.Groups.Item("value").Value)
The error I'm getting is:
System.NotSupportedException: This value may not be mutated
at [email protected]_Item(K key, V value)
at <StartupCode$FSI_0036>[email protected](Match mtch)
at Microsoft.FSharp.Collections.SeqModule.iter[T](FastFunc`2 action, IEnumerable`1 sequence)
at FSI_0036.Utilities.Config..ctor(Option`1 fileName)
at <StartupCode$FSI_0041>.$FSI_0041.main@()
stopped due to error
f# has two common associative data structures:
The one you are most used to, the mutable Dictionary which it inherits that's to it's presence in the BCL and uses a hashtable under the hood.
let dict = new System.Collections.Generic.Dictionary<string,int>()
dict.["everything"] <- 42
The other is known as Map and is, in common functional style, immutable and implemented with binary trees.
Instead of operations that would change a Dictionary, maps provide operations which return a new map which is the result of whatever change was requested. In many cases, under the hood there is no need to make an entirely new copy of the entire map, so those parts that can be shared normally are. For example:
let withDouglasAdams = Map.add "everything" 42 Map.empty
The value withDouglasAdams
will remain forever as an association of "everything" to 42. so if you later do:
let soLong = Map.remove "everything" withDouglasAdams
Then the effect of this 'removal' is only visible via the soLong
value.
F#'s Map is, as mentioned, implemented as a binary tree. Lookup is therefore O(log n) whereas a (well behaved) dictionary should be O(1). In practice a hash based dictionary will tend to outperform the tree based one in almost all simple (low number of elements, low probability of collision) as such is commonly used. That said the immutable aspect of the Map may allow you to use it in situations where the dictionary would instead require more complex locking or to write more 'elegant' code with fewer side effects and thus it remains a useful alternative.
This is not however the source of your problem. The dict 'operator' returns an explicity immutable IDictionary<K,T>
implementation (despite not indicating this in it's documentation).
From fslib-extra-pervasives.fs (note also the use of options on the keys):
let dict l =
// Use a dictionary (this requires hashing and equality on the key type)
// Wrap keys in an Some(_) option in case they are null
// (when System.Collections.Generic.Dictionary fails). Sad but true.
let t = new Dictionary<Option<_>,_>(HashIdentity.Structural)
for (k,v) in l do
t.[Some(k)] <- v
let d = (t :> IDictionary<_,_>)
let c = (t :> ICollection<_>)
let ieg = (t :> IEnumerable<_>)
let ie = (t :> System.Collections.IEnumerable)
// Give a read-only view of the dictionary
{ new IDictionary<'key, 'a> with
member s.Item
with get x = d.[Some(x)]
and set (x,v) = raise (NotSupportedException(
"This value may not be mutated"))
...