views:

591

answers:

2

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
+4  A: 

What error do you get? I tried the following and it compiles just fine

let map = new System.Collections.Generic.Dictionary<string,int>()
map.["foo"] <- 42

EDIT Verify that this code ran just fine as well .

JaredPar
Interesting... i just didmap.["foo"] <- 42;;map.["foo"] <- 43;;OK
telesphore4
Maybe it's something else... I'd delete the post but i can't
telesphore4
+5  A: 

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 there is no need to make an entirely new copy of the entire map, those parts that can be shared normally are.

let withDouglasAdams = Mapp.add "everything" 42 Map.empty

the value withDouglasAdams will remain, forever 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

The f# 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"))
   ...
ShuggyCoUk
ShuggyCoUk
Note that lookup in a binary tree is O(log n), not O(n log n) as stated.
kvb
oops - typo thanks
ShuggyCoUk
The Beta2 docs will indicate that the resulting IDictionary is immutable - thanks for pointing out this omission in the docs for the 'dict' method.
Brian
awesome - cheers Brian
ShuggyCoUk