www.digitalmars.com         C & C++   DMDScript  

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

reply John Colvin <john.loughran.colvin gmail.com> writes:
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.
Jan 12
next sibling parent reply Ilya Yaroshenko <ilyayaroshenko gmail.com> writes:
On Tuesday, 12 January 2016 at 18:27:15 UTC, John Colvin wrote:
 Background:
 Some important properties for binary relations on sets that are 
 somewhat similar to the normal ≤/≥ on the real numbers or 
 integers are:

 [...]
http://dlang.org/phobos/std_math.html#.cmp ? --Ilya
Jan 12
parent John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 18:36:32 UTC, Ilya Yaroshenko 
wrote:
 On Tuesday, 12 January 2016 at 18:27:15 UTC, John Colvin wrote:
 Background:
 Some important properties for binary relations on sets that 
 are somewhat similar to the normal ≤/≥ on the real numbers or 
 integers are:

 [...]
http://dlang.org/phobos/std_math.html#.cmp ? --Ilya
That doesn't solve the whole problem, because std.math.cmp isn't the default comparator you can't use a totally ordered float type as a drop-in for the builtin float types. A more interesting question it bring up though is: does the approach of imposing a (somewhat arbitrary) total order work for other types where you would normally use a less "strict" ordering? Does it work well for missing data representations?
Jan 12
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 01/12/2016 01:27 PM, John Colvin wrote:
 Preorder or partial order: not possible in D, opCmp insists on totality.
The way I look at it, a partial order would implement opCmp and opEqual such that a < b, b < a, and a == b are simultaneously false for unordered objects. Would that float your boat? -- Andrei
Jan 12
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 19:00:11 UTC, Andrei Alexandrescu 
wrote:
 On 01/12/2016 01:27 PM, John Colvin wrote:
 Preorder or partial order: not possible in D, opCmp insists on 
 totality.
The way I look at it, a partial order would implement opCmp and opEqual such that a < b, b < a, and a == b are simultaneously false for unordered objects. Would that float your boat? -- Andrei
a<=b and b<=a must also be false. That would work for a partial order, yes. Unfortunately, that's not possible with the current opCmp design, hence my 2 suggestions for improvements (I'm pretty sure the second one is better). The key thing is to have a design that doesn't enforce totality.
Jan 12
next sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 19:13:29 UTC, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:00:11 UTC, Andrei 
 Alexandrescu wrote:
 On 01/12/2016 01:27 PM, John Colvin wrote:
 Preorder or partial order: not possible in D, opCmp insists 
 on totality.
The way I look at it, a partial order would implement opCmp and opEqual such that a < b, b < a, and a == b are simultaneously false for unordered objects. Would that float your boat? -- Andrei
a<=b and b<=a must also be false. That would work for a partial order, yes. Unfortunately, that's not possible with the current opCmp design, hence my 2 suggestions for improvements (I'm pretty sure the second one is better). The key thing is to have a design that doesn't enforce totality.
s/totality/reflexivity which also implies it can't force totality. Note that a non-reflexive <= doesn't imply anything about ==.
Jan 12
parent reply Fool <fool dlang.org> writes:
On Tuesday, 12 January 2016 at 19:21:47 UTC, John Colvin wrote:
 Note that a non-reflexive <= doesn't imply anything about ==.
Non-reflexive '<=' does not make any sense at all.
Jan 12
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 19:44:18 UTC, Fool wrote:
 On Tuesday, 12 January 2016 at 19:21:47 UTC, John Colvin wrote:
 Note that a non-reflexive <= doesn't imply anything about ==.
Non-reflexive '<=' does not make any sense at all.
It might be a bit of a mess, agreed, but nonetheless: assert(!(float.nan <= float.nan));
Jan 12
parent reply Fool <fool dlang.org> writes:
On Tuesday, 12 January 2016 at 19:46:47 UTC, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:44:18 UTC, Fool wrote:
 Non-reflexive '<=' does not make any sense at all.
It might be a bit of a mess, agreed, but nonetheless: assert(!(float.nan <= float.nan));
Agreed, but in case of float '<=' is not an order at all.
Jan 12
parent reply Fool <fool dlang.org> writes:
On Tuesday, 12 January 2016 at 19:48:35 UTC, Fool wrote:
 On Tuesday, 12 January 2016 at 19:46:47 UTC, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:44:18 UTC, Fool wrote:
 Non-reflexive '<=' does not make any sense at all.
It might be a bit of a mess, agreed, but nonetheless: assert(!(float.nan <= float.nan));
Agreed, but in case of float '<=' is not an order at all.
By the way, that implies that the result of sorting an array of float by default comparison is undefined unless the array does not contain NaN.
Jan 12
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 19:50:57 UTC, Fool wrote:
 On Tuesday, 12 January 2016 at 19:48:35 UTC, Fool wrote:
 On Tuesday, 12 January 2016 at 19:46:47 UTC, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:44:18 UTC, Fool wrote:
 Non-reflexive '<=' does not make any sense at all.
It might be a bit of a mess, agreed, but nonetheless: assert(!(float.nan <= float.nan));
Agreed, but in case of float '<=' is not an order at all.
By the way, that implies that the result of sorting an array of float by default comparison is undefined unless the array does not contain NaN.
Didn't think of that. Yikes. Should we change the default predicate of std.algorithm.sort to std.math.cmp when ElementType!R is floating point?
Jan 12
next sibling parent Fool <fool dlang.org> writes:
On Tuesday, 12 January 2016 at 21:06:40 UTC, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:50:57 UTC, Fool wrote:
 By the way, that implies that the result of sorting an array 
 of float by default comparison is undefined unless the array 
 does not contain NaN.
Didn't think of that. Yikes. Should we change the default predicate of std.algorithm.sort to std.math.cmp when ElementType!R is floating point?
That depends on whether marketing decides to emphasize safety over performance. I'm glad that I'm not in charge! ;-)
Jan 12
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 01/12/2016 04:06 PM, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:50:57 UTC, Fool wrote:
 On Tuesday, 12 January 2016 at 19:48:35 UTC, Fool wrote:
 On Tuesday, 12 January 2016 at 19:46:47 UTC, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:44:18 UTC, Fool wrote:
 Non-reflexive '<=' does not make any sense at all.
It might be a bit of a mess, agreed, but nonetheless: assert(!(float.nan <= float.nan));
Agreed, but in case of float '<=' is not an order at all.
By the way, that implies that the result of sorting an array of float by default comparison is undefined unless the array does not contain NaN.
Didn't think of that. Yikes. Should we change the default predicate of std.algorithm.sort to std.math.cmp when ElementType!R is floating point?
We're fine as we are. By default sort compares with "<". -- Andrei
Jan 12
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 01/12/2016 02:13 PM, John Colvin wrote:
 a<=b and b<=a must also be false.
Would the advice "Only use < and == for partially-ordered data" work? -- Andrei
Jan 12
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 19:28:36 UTC, Andrei Alexandrescu 
wrote:
 On 01/12/2016 02:13 PM, John Colvin wrote:
 a<=b and b<=a must also be false.
Would the advice "Only use < and == for partially-ordered data" work? -- Andrei
If by that you mean "Only use <= or >= on data that defines a total ordering"* I guess it would work, but it has some pretty big downsides: 1) Annoying to use. 2) You have to use the opCmp return 0 (which normally means a[<>]=b && b[<>]=a) to mean "not comparable". 3) Not enforceable. Because of 2 you'll always get true if you use >= or <= on any a pair that doesn't have a defined ordering. 4) inefficient (have to do both < and == separately which can be a lot more work than <=). *would be safer to say "types that define", but strictly speaking...
Jan 12
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 01/12/2016 03:01 PM, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:28:36 UTC, Andrei Alexandrescu wrote:
 On 01/12/2016 02:13 PM, John Colvin wrote:
 a<=b and b<=a must also be false.
Would the advice "Only use < and == for partially-ordered data" work? -- Andrei
If by that you mean "Only use <= or >= on data that defines a total ordering"* I guess it would work, but it has some pretty big downsides: 1) Annoying to use. 2) You have to use the opCmp return 0 (which normally means a[<>]=b && b[<>]=a) to mean "not comparable". 3) Not enforceable. Because of 2 you'll always get true if you use >= or <= on any a pair that doesn't have a defined ordering. 4) inefficient (have to do both < and == separately which can be a lot more work than <=). *would be safer to say "types that define", but strictly speaking...
I'd be in favor of giving people the option to disable the use of <= and
= for specific data. It's a simple and logical approach. -- Andrei
Jan 12
next sibling parent reply Fool <fool dlang.org> writes:
On Tuesday, 12 January 2016 at 20:04:26 UTC, Andrei Alexandrescu 
wrote:
 I'd be in favor of giving people the option to disable the use 
 of <= and >= for specific data. It's a simple and logical 
 approach. -- Andrei
But doesn't the symbol <= originate from ORing < and = ?
Jan 12
next sibling parent Fool <fool dlang.org> writes:
On Tuesday, 12 January 2016 at 20:10:11 UTC, Fool wrote:
 But doesn't the symbol <= originate from ORing < and = ?
'=' in the mathematical sense.
Jan 12
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 01/12/2016 03:10 PM, Fool wrote:
 On Tuesday, 12 January 2016 at 20:04:26 UTC, Andrei Alexandrescu wrote:
 I'd be in favor of giving people the option to disable the use of <=
 and >= for specific data. It's a simple and logical approach. -- Andrei
But doesn't the symbol <= originate from ORing < and = ?
D uses !(b < a) for a <= b. We can invent notation to disallow that rewrite. Anyhow the use of <, >, <=, and >= for partially ordered types is bound to be less than smooth. Math papers and books often use other notations (such as rounded or square less-than) to denote operators for partially ordered data, exactly because denoting them with the classic notation may confuse the reader. Andrei
Jan 12
parent Fool <fool dlang.org> writes:
On Tuesday, 12 January 2016 at 20:25:25 UTC, Andrei Alexandrescu 
wrote:
 D uses !(b < a) for a <= b. We can invent notation to disallow 
 that rewrite.

 Anyhow the use of <, >, <=, and >= for partially ordered types 
 is bound to be less than smooth. Math papers and books often 
 use other notations (such as rounded or square less-than) to 
 denote operators for partially ordered data, exactly because 
 denoting them with the classic notation may confuse the reader.


 Andrei
It is perfectly fine to use !(b < a) for a <= b. But as John has pointed out this is sensible only if '<=' is total. Personally, I'm unsure about the best solution for D. I understand Walter's argument to 'keep it simple' and do not support non-total opCmp. On the other hand it is a bit unsatisfactory that one cannot write a custom type that behaves like float.
Jan 12
prev sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 20:04:26 UTC, Andrei Alexandrescu 
wrote:
 On 01/12/2016 03:01 PM, John Colvin wrote:
 On Tuesday, 12 January 2016 at 19:28:36 UTC, Andrei 
 Alexandrescu wrote:
 On 01/12/2016 02:13 PM, John Colvin wrote:
 a<=b and b<=a must also be false.
Would the advice "Only use < and == for partially-ordered data" work? -- Andrei
If by that you mean "Only use <= or >= on data that defines a total ordering"* I guess it would work, but it has some pretty big downsides: 1) Annoying to use. 2) You have to use the opCmp return 0 (which normally means a[<>]=b && b[<>]=a) to mean "not comparable". 3) Not enforceable. Because of 2 you'll always get true if you use >= or <= on any a pair that doesn't have a defined ordering. 4) inefficient (have to do both < and == separately which can be a lot more work than <=). *would be safer to say "types that define", but strictly speaking...
I'd be in favor of giving people the option to disable the use of <= and >= for specific data. It's a simple and logical approach. -- Andrei
Having thought about this a bit more, it doesn't fix the problem: It doesn't enable custom float types that are on par with builtins, doesn't enable transparent "missing-value" types and doesn't make tsbockmans checked integer types (or other custom types) work properly and transparently with builtin floats. The points 1, 2 and 4 from above still stand. Also - the big problem - it requires antisymmetry, which means no preorders. One of the great things about D's opCmp and opEquals is that it separates `a==b` from `a<=b && b<=a`, which enables it to express types without antisymmetric ordering (see original post for examples), what you're describing would be a frustrating situation where you have to choose between breaking antisymmetry and breaking totality, but never both. Please consider the second design I proposed? It's small, simple, has no impact on existing code and works in the right direction (library types can emulate / act as replacements for builtins) as opposed to the other way (library types are second class).
Jan 12
next sibling parent reply tsbockman <thomas.bockman gmail.com> writes:
On Tuesday, 12 January 2016 at 20:56:41 UTC, John Colvin wrote:
 Please consider the second design I proposed? It's small, 
 simple, has no impact on existing code and works in the right 
 direction (library types can emulate / act as replacements for 
 builtins) as opposed to the other way (library types are second 
 class).
If non-total ordering is going to be supported, I don't understand what's wrong with just allowing this: bool opCmp(string op, T)(T right) const { } As an alternative to the current: bool opEquals(T)(T right) const { } int opCmp(T)(T right) const { } Make it a compile-time error for a type to implement both. There is no need to deprecate the current system - people can even be encouraged to continue using it, in the very common case where it can actually express the desired logic. This approach is simple and breaks no existing code. It is also optimally efficient with respect to runtime performance.
Jan 12
parent John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 21:12:08 UTC, tsbockman wrote:
 On Tuesday, 12 January 2016 at 20:56:41 UTC, John Colvin wrote:
 Please consider the second design I proposed? It's small, 
 simple, has no impact on existing code and works in the right 
 direction (library types can emulate / act as replacements for 
 builtins) as opposed to the other way (library types are 
 second class).
If non-total ordering is going to be supported, I don't understand what's wrong with just allowing this: bool opCmp(string op, T)(T right) const { } As an alternative to the current: bool opEquals(T)(T right) const { } int opCmp(T)(T right) const { } Make it a compile-time error for a type to implement both. There is no need to deprecate the current system - people can even be encouraged to continue using it, in the very common case where it can actually express the desired logic. This approach is simple and breaks no existing code. It is also optimally efficient with respect to runtime performance.
I would kindof like that (it would definitely allow me to do what I want, as well as anything else I have failed to notice I need yet), but it flies quite strongly against Walter's (and mine to some extent) views that we'll only end up with C++-like abuse of the overloading if we allow that. Having > and < overloaded separately is asking for trouble. Another possibility would be to introduce opCmpEquals(T)(T rhs) to handle [<>]= explicitly.
Jan 12
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 01/12/2016 03:56 PM, John Colvin wrote:
 Please consider the second design I proposed?
I don't think it solves a large problem. -- Andrei
Jan 12
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 22:28:13 UTC, Andrei Alexandrescu 
wrote:
 On 01/12/2016 03:56 PM, John Colvin wrote:
 Please consider the second design I proposed?
I don't think it solves a large problem. -- Andrei
Ok. Would you consider any solution, or is that a "leave it broken"? I think I can find a way around the problem for my purposes in the short term. However, for other people implementing custom types I think it is important, it's a dirty corner that needs sorting out. The more you get to know D, the more of them you find, the more frustrating it gets seeing they aren't likely to get fixed...
Jan 12
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 01/12/2016 06:52 PM, John Colvin wrote:
 On Tuesday, 12 January 2016 at 22:28:13 UTC, Andrei Alexandrescu wrote:
 On 01/12/2016 03:56 PM, John Colvin wrote:
 Please consider the second design I proposed?
I don't think it solves a large problem. -- Andrei
Ok. Would you consider any solution, or is that a "leave it broken"?
I'd leave it to a named function. Using the built-in comparison for exotic orderings is bound to confuse users. BTW not sure you know, but D used to have a number of floating point operators like !<>=. Even those didn't help. -- Andrei
Jan 12
next sibling parent tsbockman <thomas.bockman gmail.com> writes:
On Wednesday, 13 January 2016 at 00:31:48 UTC, Andrei 
Alexandrescu wrote:
 I'd leave it to a named function. Using the built-in comparison 
 for exotic orderings is bound to confuse users. BTW not sure 
 you know, but D used to have a number of floating point 
 operators like !<>=. Even those didn't help. -- Andrei
Although I would have use for "exotic orderings" in some of my own code, I think this is the right decision. Really the only reason I'm tempted to say they should be allowed, is to smooth interaction with floating-point. But, I think what that really means is that the design of the floating-point comparisons is bad. (Which is not D's fault, I know.)
Jan 12
prev sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Wednesday, 13 January 2016 at 00:31:48 UTC, Andrei 
Alexandrescu wrote:
 On 01/12/2016 06:52 PM, John Colvin wrote:
 On Tuesday, 12 January 2016 at 22:28:13 UTC, Andrei 
 Alexandrescu wrote:
 On 01/12/2016 03:56 PM, John Colvin wrote:
 Please consider the second design I proposed?
I don't think it solves a large problem. -- Andrei
Ok. Would you consider any solution, or is that a "leave it broken"?
I'd leave it to a named function. Using the built-in comparison for exotic orderings is bound to confuse users. BTW not sure you know, but D used to have a number of floating point operators like !<>=. Even those didn't help. -- Andrei
I would completely agree, except that we have builtin types that don't obey this rule. I'd be all in favour of sticking with total orders, but it does make it hard (impossible?) to make a proper drop-in replacement for the builtin floating point numbers (including wrappers, e.g. std.typecons.Typedef can't handle nans correctly) or to properly handle comparisons between custom types and builtin floating points (as mentioned by tsbockman). I am all for keeping it simple here, but I still think there's a problem.
Jan 12
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Wednesday, 13 January 2016 at 01:39:26 UTC, John Colvin wrote:
 On Wednesday, 13 January 2016 at 00:31:48 UTC, Andrei 
 Alexandrescu wrote:
 [...]
I would completely agree, except that we have builtin types that don't obey this rule. I'd be all in favour of sticking with total orders, but it does make it hard (impossible?) to make a proper drop-in replacement for the builtin floating point numbers (including wrappers, e.g. std.typecons.Typedef can't handle nans correctly) or to properly handle comparisons between custom types and builtin floating points (as mentioned by tsbockman). I am all for keeping it simple here, but I still think there's a problem.
https://issues.dlang.org/show_bug.cgi?id=15561
Jan 12
next sibling parent reply tsbockman <thomas.bockman gmail.com> writes:
On Wednesday, 13 January 2016 at 01:43:21 UTC, John Colvin wrote:
 I am all for keeping it simple here, but I still think there's 
 a problem.
https://issues.dlang.org/show_bug.cgi?id=15561
That's a good point.
Jan 12
parent Dominikus Dittes Scherkl <Dominikus.Scherkl continental-corporation.com> writes:
On Wednesday, 13 January 2016 at 02:12:36 UTC, tsbockman wrote:
 On Wednesday, 13 January 2016 at 01:43:21 UTC, John Colvin 
 wrote:
 I am all for keeping it simple here, but I still think 
 there's a problem.
https://issues.dlang.org/show_bug.cgi?id=15561
That's a good point.
Interesting. I often use partially-ordered objects in my code, and therefore define opCmp to return float, making use of the NaN value. But then I also define opEquals to return false for (NaN == NaN), and my custom types work as intended. In fact, the existance of the special floatingpoint operators like !<> (and beeing able to overload them) was one of the main reasons for me, to start using D. For me it's a mayor issue if those operators don't work correct. I know they are deprecated, but I don't know why. As was pointed out they are necessary if you want to implement something partially ordered. That not everybody needs this is no valid reason to deprecate it. I hated to be told I should not define opCmp to return float instead of int, as was also propagated by the "learning D" book. If this is the common state of the art, I will drop D and start using my own fork the moment they are not supported anymore.
Jan 12
prev sibling parent John Colvin <john.loughran.colvin gmail.com> writes:
On Wednesday, 13 January 2016 at 01:43:21 UTC, John Colvin wrote:
 On Wednesday, 13 January 2016 at 01:39:26 UTC, John Colvin 
 wrote:
 On Wednesday, 13 January 2016 at 00:31:48 UTC, Andrei 
 Alexandrescu wrote:
 [...]
I would completely agree, except that we have builtin types that don't obey this rule. I'd be all in favour of sticking with total orders, but it does make it hard (impossible?) to make a proper drop-in replacement for the builtin floating point numbers (including wrappers, e.g. std.typecons.Typedef can't handle nans correctly) or to properly handle comparisons between custom types and builtin floating points (as mentioned by tsbockman). I am all for keeping it simple here, but I still think there's a problem.
https://issues.dlang.org/show_bug.cgi?id=15561
https://github.com/D-Programming-Language/phobos/pull/3927
Jan 13
prev sibling next sibling parent tsbockman <thomas.bockman gmail.com> writes:
On Tuesday, 12 January 2016 at 18:27:15 UTC, John Colvin wrote:
 P.S. This is not just about floats!
This also affects any custom numeric type which should be comparable with float - while working on a checked integer type for Phobos, one of the (minor) problems I have run into is that it is impossible to reproduce the comparison behaviour of the built-in integers with respect to floating-point values - even though the latest version of my checked integer type actually has no "NaN" state of its own.
Jan 12
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 01/12/2016 07:27 PM, John Colvin wrote:
 ...
struct S{ auto opCmp(S rhs){ return float.nan; } bool opEquals(S rhs){ return false; } } unittest{ S a,b; assert(!(a==b)); assert(!(a<b)); assert(!(a<=b)); assert(!(a>b)); assert(!(a>=b)); }
Jan 12
next sibling parent John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 20:52:51 UTC, Timon Gehr wrote:
 On 01/12/2016 07:27 PM, John Colvin wrote:
 ...
struct S{ auto opCmp(S rhs){ return float.nan; } bool opEquals(S rhs){ return false; } } unittest{ S a,b; assert(!(a==b)); assert(!(a<b)); assert(!(a<=b)); assert(!(a>b)); assert(!(a>=b)); }
Interesting, I'll have to think more about this. Pretty ugly to have to use floating point instructions for every comparison, no matter the actually data, but maybe there's something here...
Jan 12
prev sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 20:52:51 UTC, Timon Gehr wrote:
 On 01/12/2016 07:27 PM, John Colvin wrote:
 ...
struct S{ auto opCmp(S rhs){ return float.nan; } bool opEquals(S rhs){ return false; } } unittest{ S a,b; assert(!(a==b)); assert(!(a<b)); assert(!(a<=b)); assert(!(a>b)); assert(!(a>=b)); }
what about classes and Object.opCmp?
Jan 12
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 01/12/2016 10:02 PM, John Colvin wrote:
 On Tuesday, 12 January 2016 at 20:52:51 UTC, Timon Gehr wrote:
 On 01/12/2016 07:27 PM, John Colvin wrote:
 ...
struct S{ auto opCmp(S rhs){ return float.nan; } bool opEquals(S rhs){ return false; } } unittest{ S a,b; assert(!(a==b)); assert(!(a<b)); assert(!(a<=b)); assert(!(a>b)); assert(!(a>=b)); }
what about classes and Object.opCmp?
You can introduce a new opCmp signature in your subclass, but == is enforced to be reflexive for class objects. So this approach only really works for structs. (And for structs, it is obviously a hack.)
Jan 12
parent John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 12 January 2016 at 21:27:38 UTC, Timon Gehr wrote:
 On 01/12/2016 10:02 PM, John Colvin wrote:
 On Tuesday, 12 January 2016 at 20:52:51 UTC, Timon Gehr wrote:
 On 01/12/2016 07:27 PM, John Colvin wrote:
 ...
struct S{ auto opCmp(S rhs){ return float.nan; } bool opEquals(S rhs){ return false; } } unittest{ S a,b; assert(!(a==b)); assert(!(a<b)); assert(!(a<=b)); assert(!(a>b)); assert(!(a>=b)); }
what about classes and Object.opCmp?
You can introduce a new opCmp signature in your subclass, but == is enforced to be reflexive for class objects. So this approach only really works for structs. (And for structs, it is obviously a hack.)
I actually quite like it. Also, checking the generated assembly for an IntWithNaN type: no floating point comparisons in sight (with gdc and ldc at least, don't really care if dmd does something a bit dumb)
Jan 13