Question

In answering this question, I discovered the following behaviour of compare on discriminated unions.

type T = A | B | C | D 
compare A B   (* val it : int = -1 *)
compare A C   (* val it : int = -2 *)
compare A D   (* val it : int = -3 *)

I was surprised by this.

Can I rely on compare measuring the "distance" between constructors like this?

The spec says (p. 154) about the generated compareTo:

If T is a union type, invoke Microsoft.FSharp.Core.Operators.compare first on the index of the union cases for the two values, and then on each corresponding field pair of x and y for the data carried by the union case. Return the first non-zero result.

From that, I'd expect compare on type T to always give one of -1,0,1 since that's how compare behaves on numeric types. (Right?)

Was it helpful?

Solution

The quote from the specification says that the generated comparison will first compare the tags (that is essentially the index of the constructors), but I'm not sure if this gives you any useful information - because if the union carries some value, you will not know whether the number is distance between the constructors, or the result of the comparison of the contained values. For example:

type Tricky() = 
  interface System.IComparable with
    override x.CompareTo(b) = -2

type DU = 
 | A of Tricky
 | B 
 | C

// Returns -2 because of the distance between constructors
compare (A (Tricky())) C
// Returns -2 because of the comparison on `Tricky` objects
compare (A (Tricky())) (A(Tricky()))

If you wanted to rely on the ability to get the distance between constructors, it might be safer to use enumerations:

type DU = 
 | A = 1
 | B = 2 
 | C = 3

Then you can get the distance by converting the values to integers using (int DU.A) - (int DU.C).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top