Dulan
Dulan

Reputation: 362

Overloading equality F# operator on a discriminated union with tuple gives an unexpected result

Ok, so to make the complicated title more clear: I have a single-case union that's a generic tuple. The type also overloads the equality operator with the intent on making something like Edge (1, 2) equivalent to Edge (2, 1).

type Edge<'a> = Edge of 'a * 'a
    with
        static member (=) (e1: Edge<_>, e2: Edge<_>) =
            match e1, e2 with
            | Edge(a,b), Edge(c,d) ->
                (a = c && b = d) || (a = d && b = c)

However, when I go to make two values of the Edge type that should be equivalent and compare them, it returns false.

> let e1 = Edge (1,2);;

val e1 : Edge<int> = Edge (1,2)

> let e2 = Edge (2,1);;

val e2 : Edge<int> = Edge (2,1)

> e1 = e2;;

val it : bool = false

I'm at a loss as to what's actually happening here. Is there something in particular about the equality (=) operator that makes it more complex to override than other operators?

Upvotes: 4

Views: 703

Answers (1)

John Palmer
John Palmer

Reputation: 25516

So there was a hint in the error message (which you didn't post)

warning FS0086: The name '(=)' should not be used as a member name. To define equality semantics for a type, override the 'Object.Equals' member. If defining a static member for use from other CLI languages then use the name 'op_Equality' instead.

You can actually check by adding printf calls in your original code and you will find that it is actually never called.

So then you try this:

type Edge<'a> = Edge of 'a * 'a
     with
         override x.Equals (y: obj ) =
             match  y with
             | :? Edge<'a> as e ->
                 match x,e with
                 |Edge(a,b),Edge(c,d)->(a = c && b = d) || (a = d && b = c)
             | _ -> false

which also fails. The compiler error messages then walk you through a few more error messages until you get to

[<CustomEquality;NoComparison>]
type Edge<'a when 'a:equality> = Edge of 'a * 'a
    with
        override x.Equals (y: obj ) =
            match  y with
            | :? Edge<'a> as e ->
                match x,e with
                |Edge(a,b),Edge(c,d)->(a = c && b = d) || (a = d && b = c)
            | _ -> false

which works just fine. The equals function is a little more complex than many of the other operators, so overriding it takes a little more effort.

Upvotes: 5

Related Questions