How do you declare the values of a dictionary entry as mutable?

13,126

Solution 1

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"))
   ...

Solution 2

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 .

Share:
13,126

Related videos on Youtube

telesphore4
Author by

telesphore4

Updated on November 05, 2020

Comments

  • telesphore4
    telesphore4 over 3 years

    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
    
  • telesphore4
    telesphore4 over 14 years
    Interesting... i just did map.["foo"] <- 42;; map.["foo"] <- 43;; OK
  • ShuggyCoUk
    ShuggyCoUk over 14 years
    the research sites uptime is really rather poor. here's the google cache link temporarily 209.85.229.132/…
  • kvb
    kvb over 14 years
    Note that lookup in a binary tree is O(log n), not O(n log n) as stated.
  • Prakash
    Prakash over 14 years
    The Beta2 docs will indicate that the resulting IDictionary is immutable - thanks for pointing out this omission in the docs for the 'dict' method.

Related