opCmp, [partial/total/pre]orders, custom floating point types etc.

John Colvin via Digitalmars-d digitalmars-d at puremagic.com
Tue Jan 12 10:27:15 PST 2016


Background:
Some important properties for binary relations on sets that are 
somewhat similar to the normal ≤/≥ on the real numbers or 
integers are:

a ≤ a (reflexivity);
if a ≤ b and b ≤ a, then a = b (antisymmetry);
if a ≤ b and b ≤ c, then a ≤ c (transitivity);
a ≤ b or b ≤ a (totality, implies reflexivity);

Definitions:
A preorder obeys reflexivity and transitivity.
A partial order obeys reflexivity, transitivity and antisymmetry.
A total order obeys transitivity, antisymmetry and totality.
A total preorder obeys transitivity and totality but not 
antisymmetry

Examples:
Arrays ordered by length, vectors ordered by euclidian length, 
complex numbers ordered by absolute value etc. are all total 
preorders.
Integers with ≤ or ≥ form a total order.
float/double/real obey antisymmetry and transitivity but not 
reflexivity or totality.

Implementations in D:
Total order: opCmp with "consistent" opEquals to enforce 
antisymmetry.

Total preorder: opCmp with "inconsistent" opEquals to break 
antisymmetry.

Preorder or partial order: not possible in D, opCmp insists on 
totality.

Antisymmetry and transitivity but not reflexivity or totality, 
e.g. custom float: not possible in D, opCmp insists on totality 
(no way for opCmp to signify nan comparisons, either with nan 
(reflexivity) or others (totality & reflexivity)).


Solutions to the above problems:

1) opCmp - or some extended, renamed version of it - needs 4 
return values: greater, lesser, equal and 
neither/unequal/incomparible. This would be the value that is 
returned when e.g. either side is nan.

or, less intrusively and more (runtime) efficiently:

2) Introduce a new special function `bool opCmpOrdered(T rhs)` 
that, if defined, is used to shortcircuit a comparison. Any 
previous lowering to `a.opCmp(b) [<>]=? 0` (as in 
https://dlang.org/spec/operatoroverloading.html#compare) would 
now lower to `a.opCmpOrdered(b) && a.opCmp(b) [<>]=? 0`. E.g. `a 
 >= b` becomes `a.opCmpOrdered(b) && a.opCmp(b) >= 0`. If 
opCmpOrdered isn't defined the lowering is unchanged from before 
(or opCmpOrdered defaults to true, same thing...).

Bigger example: a custom float type

struct MyFloat
{
     // ...
     bool isNaN() { /* ... */ }
     bool opCmpOrdered(MyFloat rhs)
     {
         if (this.isNaN || rhs.isNaN) return false;
         else return true;
     }
     int opCmp(MyFloat rhs)
     { //can assume neither are nan
         /* ...  */
     }
     bool opEquals(MyFloat rhs)
     {
         if (this.isNaN || rhs.isNaN) return false;
         else /* ... */
     }
}

unittest
{
     MyFloat a, b; // has .init as nan, of course :)

     static allFail(MyFloat a, MyFloat b)
     {
         // all of these should short-circuit because
         // opCmpOrdered will return false
         assert(!(a==b));
         assert(!(a<b));
         assert(!(a<=b));
         assert(!(a>b));
         assert(!(a>=b));
     }

     allFail(a, b);
     a = 3;
     allFail(a, b);

     b = 4;
     assert(a!=b);
     assert(a<b);
     assert(a<=b);
     assert(!(a>b));
     assert(!(a>=b));

     a = 4;
     assert(a==b);
     assert(!(a<b));
     assert(a<=b);
     assert(!(a>b));
     assert(a>=b);
}


P.S. This is not just about floats! It is also very useful for 
making types that represent missing data (e.g. encapsulating 
using int.min for a missing value). I can only come up with 
strained examples for preorders and partial orders that I would 
want people using < and > for, so I won't speak of them here.

P.P.S. Note that I am *not* trying to extend D's operator 
overloading to make > and < usable for arbitrary binary 
relations, like in C++. This small change is strictly within the 
realm of what <, > and = are already used for (in D, with 
floats). I'm convinced that if you wouldn't read it out loud as 
something like "less/fewer/smaller than" or "greater/more/bigger 
than", you shouldn't be using < or >, you should name a separate 
function; I don't think this proposal encourages violating that 
principle.


More information about the Digitalmars-d mailing list