Search code examples
f#gethashcodeicomparablediscriminated-union

How do I Override GetHashCode and CompareTo for a Discriminated Union in F#?


I have a simple F# discriminated union that combines a bool, a string, and a float. I want to override the Object.Equals(arg) of this union so that I can put in an epsilon to account for precision errors when checking for float equality. The compiler complained that I if I override this.Equals(arg), I should also override this.GetHashCode() and this.CompareTo(arg). For these to overrides, I have no special functionality planned, so I'd simply like to call the default versions of these methods. In my implementation now, I have three calls to GetHashCode and three calls to CompareTo for each type in my discriminated union: one for each type.

Is there a way to code the GetHashCode override with just a single call to GetHashCode? Same question for CompareTo? All of the types in my discriminated union implement ICompareable.

[<CustomEquality;CustomComparison>]
type MyType =
    | Bool of bool
    | Str of string
    | Float of float

    override this.Equals(arg) =
        let epsilon = 0.1
        match arg with
        | :? MyType as other ->
            match this, other with
            | Bool(a), Bool(b) -> a = b
            | Str(a), Str(b) -> a = b
            | Float(a), Float(b) -> Math.Abs(a - b) < epsilon
            | _ -> false
        | _ -> false

    override this.GetHashCode() =
        match this with
        // Three calls to GetHashCode.  I'd like only one
        | Bool(a) -> a.GetHashCode()
        | Str(a) -> a.GetHashCode()
        | Float(a) -> a.GetHashCode()
        | _ -> 0

    interface System.IComparable with
        member this.CompareTo arg =
            match arg with
                | :? MyType as other ->
                    match this, other with
                    // Three calls to CompareTo.  I'd like only one
                    | Bool(a), Bool(b) -> a.CompareTo(b)
                    | Str(a), Str(b) -> a.CompareTo(b)
                    | Float(a), Float(b) -> a.CompareTo(b)
                    | _ -> 0
                | _ -> 0

Solution

  • You could define a helper property that extracts the contents of the DU as obj:

    member this.Value = 
      match this with
      | Bool(b) -> box b
      | Str(s) -> box s
      | Float(f) -> box f
    

    Then you can implement GetHashCode just by getting the value (which involves some boxing, so it will be a bit slower) and calling the GetHashCode on the returned object:

    override this.GetHashCode() =
      this.Value.GetHashCode()