8

I Know F# have the MAP, but I wanna use the .NET Dictionary. This dict have key as string and values as F# values + the dict, ie:

type ExprC = 
    | StrC of string
    | BoolC of bool
    | IntC of int32
    | DecC of decimal
    | ArrayC of int * array<ExprC>
    | RelC of RelationC
and RelationC = Dictionary<string, ExprC>        

Now, the problem I wanna solve is how provide the RelationC type with structural equality. If is required to encapsulate the actual storage, how create a container that is a replacement for Dictionary, use it for mutable operations and have structural equality?


With the current answer, this code not work (of curse the implementation is not complete, however, this not even compile):

[<CustomEquality; CustomComparison>]
type MyDict() =
    inherit Dictionary<string, ExprC>()
    override this.Equals x =
        match x with
        | :? MyDict as y -> (this = y)
        | _ -> false

    override this.GetHashCode () =
        hash this

    interface System.IComparable with
      member x.CompareTo yobj =
          match yobj with
          | :? MyDict as y -> compare x y
          | _ -> invalidArg "MyDict" "cannot compare values of different types"

and [<StructuralEquality;StructuralComparison>] ExprC =
    | IntC of int
    | StrC of string
    | MapC of MyDict

This is the error:

Error FS0377: This type uses an invalid mix of the attributes 'NoEquality', 'ReferenceEquality', 'StructuralEquality', 'NoComparison' and 'StructuralComparison' (FS0377)

mamcx
  • 15,916
  • 26
  • 101
  • 189
  • 2
    Out of curiosity, is there a particular reason for wanting to do this with `Dictionary` rather than `Map`? – TheInnerLight Jan 25 '16 at 09:40
  • 1
    Performance. .NET dicts are way faster (https://stackoverflow.com/questions/3396196/f-fsharpmap-vs-dictionary-performance). In my use-case, I will need a lot of it. Plus, curiosity. I think is usefull to know how turn in a good citizen a .NET class ;) – mamcx Jan 25 '16 at 13:39
  • 2
    Have you profiled and found a performance problem or are you guessing there will be a problem in the future? If you don't know for sure if it's a problem, don't make your life difficult. Do it the obvious way and solve the problem later if you need to. – TheInnerLight Jan 25 '16 at 16:06
  • 1
    http://ericlippert.com/2012/12/17/performance-rant – Mark Seemann Jan 29 '16 at 06:33
  • 1
    For your latest issue, you can remove all of the attributes (for class types like `MyDict` they are not applicable, which is why you're seeing the error; for discriminated unions the attributes you're using just reflect the default behavior anyway). – kvb Feb 11 '16 at 23:16
  • So why the F# native map work? At the end, I just wish to have Dictionary behind the scenes to manage the data, but work with the recursive type declaration – mamcx Feb 12 '16 at 17:52

2 Answers2

3

If you absolutely must use Dictionary<string, ExprC>, you could derive from Dictionary<'k, 'v> and override Equals:

type MyDict() =
    inherit Dictionary<string, ExprC>()
    override this.Equals x =
        true // real implementation goes here
    override this.GetHashCode () =
        0 // real implementation goes here

Here, you'd need to implement Equals to have structural equality, and you'll need to implement GetHashCode to match you Equals implementation.

Another alternative, if you don't need the concrete class Dictionary<'k, 'v>, is to define your own class that implements IDictionary<TKey, TValue>.

While possible, this sounds like a lot of work. It'd be much easier to use a Map, which has structural equality by default:

let m1 = Map.ofList [("foo", 1); ("bar", 2); ("baz", 3)]
let m2 = Map.ofList [("bar", 2); ("foo", 1); ("baz", 3)]
let m3 = Map.ofList [("bar", 2); ("foo", 1); ("baz", 4)]

> m1 = m2;;
val it : bool = true
> m1 = m3;;
val it : bool = false
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
2

Regarding the question at the end of the updated original post: What is the reason for "This type uses an invalid mix..."? This is a bug in the F# compiler, the error message is misleading, see Github. The solution is to simply remove all attributes from MyDict.

Anton Schwaighofer
  • 3,119
  • 11
  • 24