Built-in compare on discriminated unions in f#
Asked Answered
P

1

6

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?)

Pecten answered 14/3, 2014 at 22:52 Comment(2)
Some numeric types implement CompareTo(other) as (this - other), for example ((short)2).CompareTo((short)5) returns -3.Octangle
@Octangle - This is an interesting point, but surprisingly, it does not apply to the compare function in F#. For example 2s.CompareTo(5s) returns -3 as you say, but compare 2s 5s returns just -1.Robena
R
5

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

Robena answered 15/3, 2014 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.