Discussion:
WAT: opCmp and opEquals woes
(too old to reply)
H. S. Teoh via Digitalmars-d
2014-07-23 16:45:59 UTC
Permalink
This morning, I discovered this major WAT in D:

----
struct S {
int x;
int y;
int opCmp(S s) {
return x - s.x; // compare only x
}
}

void main() {
auto s1 = S(1,2);
auto s2 = S(1,3);
auto s3 = S(2,1);

assert(s1 < s3); // OK
assert(s2 < s3); // OK
assert(s3 > s1); // OK
assert(s3 > s2); // OK
assert(s1 <= s2 && s2 >= s1); // OK
assert(s1 == s2); // FAIL -- WAT??
}
----

The reason for this is that the <, <=, >=, > operators are defined in
terms of opCmp (which, btw, is defined to return 0 when the objects
being compared are equal), but == is defined in terms of opEquals. When
opEquals is not defined, it defaults to the built-in compiler
definition, which is a membership equality test, even if opCmp *is*
defined, and returns 0 when the objects are equal.

Why isn't "a==b" rewritten as "a.opCmp(b)==0"?? I'm pretty sure TDPL
says this is the case (unfortunately I'm at work so I can't check my
copy of TDPL).

https://issues.dlang.org/show_bug.cgi?id=13179

:-(


T
--
English has the lovely word "defenestrate", meaning "to execute by throwing someone out a window", or more recently "to remove Windows from a computer and replace it with something useful". :-) -- John Cowan
Ary Borenszweig via Digitalmars-d
2014-07-23 17:15:10 UTC
Permalink
Post by H. S. Teoh via Digitalmars-d
----
struct S {
int x;
int y;
int opCmp(S s) {
return x - s.x; // compare only x
}
}
void main() {
auto s1 = S(1,2);
auto s2 = S(1,3);
auto s3 = S(2,1);
assert(s1 < s3); // OK
assert(s2 < s3); // OK
assert(s3 > s1); // OK
assert(s3 > s2); // OK
assert(s1 <= s2 && s2 >= s1); // OK
assert(s1 == s2); // FAIL -- WAT??
}
----
The reason for this is that the <, <=, >=, > operators are defined in
terms of opCmp (which, btw, is defined to return 0 when the objects
being compared are equal), but == is defined in terms of opEquals. When
opEquals is not defined, it defaults to the built-in compiler
definition, which is a membership equality test, even if opCmp *is*
defined, and returns 0 when the objects are equal.
Why isn't "a==b" rewritten as "a.opCmp(b)==0"?? I'm pretty sure TDPL
says this is the case (unfortunately I'm at work so I can't check my
copy of TDPL).
https://issues.dlang.org/show_bug.cgi?id=13179
:-(
T
Imagine you have a list of integers and strings denoting integers: [1,
"2", 100, "38"]. Now you want to sort them according to their numeric
value. Of course, 1 and "1" would have the same order. However, 1 and
"1" are different, so "==" would give false, while 1.opCmp("1") would
give 0.

Equality and comparison are different. opCmp is used for sorting
objects, which has nothing to do with equality. Inferring equality from
opCmp is wrong in my opinion.
Dicebot via Digitalmars-d
2014-07-23 18:09:40 UTC
Permalink
Post by Ary Borenszweig via Digitalmars-d
Imagine you have a list of integers and strings denoting
integers: [1, "2", 100, "38"]. Now you want to sort them
according to their numeric value. Of course, 1 and "1" would
have the same order. However, 1 and "1" are different, so "=="
would give false, while 1.opCmp("1") would give 0.
Equality and comparison are different. opCmp is used for
sorting objects, which has nothing to do with equality.
Inferring equality from opCmp is wrong in my opinion.
Well this is why you can actually override those :) I think
automatic opCmd -> opEqual generation covers vast majority of use
cases and as such will have a vary good effort / decreased
annoyance ratio.
David Gileadi via Digitalmars-d
2014-07-23 18:26:16 UTC
Permalink
Post by Ary Borenszweig via Digitalmars-d
Imagine you have a list of integers and strings denoting integers: [1,
"2", 100, "38"]. Now you want to sort them according to their numeric
value. Of course, 1 and "1" would have the same order. However, 1 and
"1" are different, so "==" would give false, while 1.opCmp("1") would
give 0.
Equality and comparison are different. opCmp is used for sorting
objects, which has nothing to do with equality. Inferring equality
from opCmp is wrong in my opinion.
Well this is why you can actually override those :) I think automatic
opCmd -> opEqual generation covers vast majority of use cases and as
such will have a vary good effort / decreased annoyance ratio.
I agree. In fact I think if you've implemented opCmp to sort 1 and "1"
as equal that in most cases you'd expect "1" and 1 to compare as
logically equal. Automatic opCmp -> opEquals seems like a very sane
default to me.
Ali Çehreli via Digitalmars-d
2014-07-23 18:56:23 UTC
Permalink
Post by David Gileadi via Digitalmars-d
Post by Ary Borenszweig via Digitalmars-d
Imagine you have a list of integers and strings denoting integers: [1,
"2", 100, "38"]. Now you want to sort them according to their numeric
value. Of course, 1 and "1" would have the same order. However, 1 and
"1" are different, so "==" would give false, while 1.opCmp("1") would
give 0.
Equality and comparison are different. opCmp is used for sorting
objects, which has nothing to do with equality. Inferring equality
from opCmp is wrong in my opinion.
Well this is why you can actually override those :) I think automatic
opCmd -> opEqual generation covers vast majority of use cases and as
such will have a vary good effort / decreased annoyance ratio.
I agree. In fact I think if you've implemented opCmp to sort 1 and "1"
as equal that in most cases you'd expect "1" and 1 to compare as
logically equal. Automatic opCmp -> opEquals seems like a very sane
default to me.
To add, C++ is getting "= default" versions:

http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2014/n3950.html

Ali
Andrei Alexandrescu via Digitalmars-d
2014-07-23 18:49:58 UTC
Permalink
Post by Ary Borenszweig via Digitalmars-d
Imagine you have a list of integers and strings denoting integers: [1,
"2", 100, "38"]. Now you want to sort them according to their numeric
value. Of course, 1 and "1" would have the same order. However, 1 and
"1" are different, so "==" would give false, while 1.opCmp("1") would
give 0.
Equality and comparison are different. opCmp is used for sorting
objects, which has nothing to do with equality. Inferring equality
from opCmp is wrong in my opinion.
Well this is why you can actually override those :) I think automatic
opCmd -> opEqual generation covers vast majority of use cases and as
such will have a vary good effort / decreased annoyance ratio.
I'd say let's leave things as they are. opEquals may need to do less
work than opCmp, and it often sees intensive use. -- Andrei
Dicebot via Digitalmars-d
2014-07-23 19:01:51 UTC
Permalink
On Wednesday, 23 July 2014 at 18:49:49 UTC, Andrei Alexandrescu
Post by Andrei Alexandrescu via Digitalmars-d
I'd say let's leave things as they are. opEquals may need to do
less work than opCmp, and it often sees intensive use. -- Andrei
You will be the one answering user complaints ;)
H. S. Teoh via Digitalmars-d
2014-07-23 19:04:37 UTC
Permalink
Post by Andrei Alexandrescu via Digitalmars-d
[1, "2", 100, "38"]. Now you want to sort them according to their
numeric value. Of course, 1 and "1" would have the same order.
However, 1 and "1" are different, so "==" would give false, while
1.opCmp("1") would give 0.
Equality and comparison are different. opCmp is used for sorting
objects, which has nothing to do with equality. Inferring equality
from opCmp is wrong in my opinion.
Well this is why you can actually override those :) I think automatic
opCmd -> opEqual generation covers vast majority of use cases and as
such will have a vary good effort / decreased annoyance ratio.
I'd say let's leave things as they are. opEquals may need to do less
work than opCmp, and it often sees intensive use. -- Andrei
If autogenerating opEquals to be opCmp()==0 is a no-go, then I'd much
rather say it should be a compile error if the user defines opCmp but
not opEquals. Currently, we have the bad situation where == behaves
inconsistently w.r.t. <, <=, >=, > because we allow opCmp to be defined
but opEquals not, and the default compiler implementation of opEquals
may or may not match the meaning of opCmp.

In short, what I'd like to see, in order of preference, is:

(1) If opCmp is defined but opEquals not, then opEquals should be
defined to be opCmp()==0.

(2) If (1) is a no-go, then the next best situation is that if opCmp is
defined but opEquals isn't, then the compiler should issue an error,
rather than implicitly generating a default opEquals that probably does
not match the programmer's expectations.

(3) If (2) is also a no-go, the 3rd best situation is that if opCmp is
defined but opEquals isn't, then the compiler should issue an error if
the user ever writes "a==b". That is, we allow the user to not define
opEquals as long as it's not actually used, but it's an error if it is
used.

(4) Do nothing, and allow the current hidden breakage to perpetuate and
make people hate D when they suddenly discover it when they forget to
implement opEquals.

I really hope we don't have to resort to (4).


T
--
All men are mortal. Socrates is mortal. Therefore all men are Socrates.
Andrei Alexandrescu via Digitalmars-d
2014-07-23 21:36:26 UTC
Permalink
Post by H. S. Teoh via Digitalmars-d
If autogenerating opEquals to be opCmp()==0 is a no-go, then I'd much
rather say it should be a compile error if the user defines opCmp but
not opEquals.
No. There is this notion of partial ordering that makes objects not
smaller and not greater than others, yet not equal. -- Andrei
Brad Roberts via Digitalmars-d
2014-07-23 21:45:22 UTC
Permalink
Post by Andrei Alexandrescu via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
If autogenerating opEquals to be opCmp()==0 is a no-go, then I'd much
rather say it should be a compile error if the user defines opCmp but
not opEquals.
No. There is this notion of partial ordering that makes objects not
smaller and not greater than others, yet not equal. -- Andrei
Right, but in that case just define both. It's not the dominant case so
shouldn't define the default behavior. Or if you truly have a case of
comparable but no possibility of equal, just @disable opEqual (or define
and throw, or assert, or...).
Jonathan M Davis via Digitalmars-d
2014-07-24 00:28:04 UTC
Permalink
On Wednesday, 23 July 2014 at 21:36:16 UTC, Andrei Alexandrescu
Post by Andrei Alexandrescu via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
If autogenerating opEquals to be opCmp()==0 is a no-go, then
I'd much
rather say it should be a compile error if the user defines
opCmp but
not opEquals.
No. There is this notion of partial ordering that makes objects
not smaller and not greater than others, yet not equal. --
I would strongly argue that if lhs.opCmp(rhs) == 0 is not
equivalent to lhs == rhs, then it that type is broken and should
not be using opCmp to do its comparisons. std.algorithm.sort
allows you to use any predicate you want, allowing for such
orderings, but it does not work with generic code for a type to
define opCmp or opEquals such that they're not consistent,
because that's not consistent with how comparisons work for the
built-in types.

- Jonathan M Davis
Andrei Alexandrescu via Digitalmars-d
2014-07-24 00:45:15 UTC
Permalink
Post by Andrei Alexandrescu via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
If autogenerating opEquals to be opCmp()==0 is a no-go, then I'd much
rather say it should be a compile error if the user defines opCmp but
not opEquals.
No. There is this notion of partial ordering that makes objects not
smaller and not greater than others, yet not equal. --
I would strongly argue that if lhs.opCmp(rhs) == 0 is not equivalent to
lhs == rhs, then it that type is broken and should not be using opCmp to
do its comparisons. std.algorithm.sort allows you to use any predicate
you want, allowing for such orderings, but it does not work with generic
code for a type to define opCmp or opEquals such that they're not
consistent, because that's not consistent with how comparisons work for
the built-in types.
std.algorithm.sort does not use equality at all. It just deems objects
for which pred(a, b) and pred(b, a) as unordered. -- Andrei
Jonathan M Davis via Digitalmars-d
2014-07-24 01:12:58 UTC
Permalink
On Thursday, 24 July 2014 at 00:45:05 UTC, Andrei Alexandrescu
Post by Andrei Alexandrescu via Digitalmars-d
std.algorithm.sort does not use equality at all. It just deems
objects for which pred(a, b) and pred(b, a) as unordered. --
I know. It uses < by default. What I'm disputing is that it's
okay to define a type whose opCmp does not match its opEquals -
or even that it has opCmp but doesn't have opEquals. I would
strongly argue that such a type should not use opCmp for its
ordering, because otherwise, its comparison operators are not
consistent with how the comparison operators work for the
built-in types.

I brought up sort, because that's usually where the question of
ordering comes up, and our sort function is designed so that it
does not require opCmp (the same with other Phobos constructs
such as RedBlackTree). So, if a type needs to do its ordering in
a manner which is inconsistent with opEquals, it can do so by
providing a function other than opCmp for those comparisons.

But I think that it's a poor idea to have opCmp not be consistent
with opEquals, since most generic code will assume that they're
consistent. I'd strongly argue that an overloaded operate should
be consistent with how the built-in operators work, or that
functionality should not be using an overloaded operator. This is
especially important when generic code comes into play, but it's
also very important with regards to understanding how code works
in general. Operators bring with them certain expectations of
behavior, and they should maintain that and not be used in a
manner which violates that. And having opCmp be inconsistent with
opEquals violates that, indicating that opCmp should not be used
in that case.

As such, I don't think that arguing that opCmp should be able to
exist without an opEquals is a bad argument. I think that if you
have opCmp, you should required to have opEquals (though not
automatically defined as lhs.opCmp(rhs) == 0, because that would
incur a silent performance hit).

- Jonathan M Davis
deadalnix via Digitalmars-d
2014-07-24 01:37:00 UTC
Permalink
Post by Jonathan M Davis via Digitalmars-d
On Wednesday, 23 July 2014 at 21:36:16 UTC, Andrei Alexandrescu
Post by Andrei Alexandrescu via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
If autogenerating opEquals to be opCmp()==0 is a no-go, then
I'd much
rather say it should be a compile error if the user defines
opCmp but
not opEquals.
No. There is this notion of partial ordering that makes
objects not smaller and not greater than others, yet not
equal. --
I would strongly argue that if lhs.opCmp(rhs) == 0 is not
equivalent to lhs == rhs, then it that type is broken and
should not be using opCmp to do its comparisons.
std.algorithm.sort allows you to use any predicate you want,
allowing for such orderings, but it does not work with generic
code for a type to define opCmp or opEquals such that they're
not consistent, because that's not consistent with how
comparisons work for the built-in types.
- Jonathan M Davis
floating point ?
Jonathan M Davis via Digitalmars-d
2014-07-24 18:35:33 UTC
Permalink
On Thursday, 24 July 2014 at 00:28:06 UTC, Jonathan M Davis
Post by Jonathan M Davis via Digitalmars-d
On Wednesday, 23 July 2014 at 21:36:16 UTC, Andrei
Post by Andrei Alexandrescu via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
If autogenerating opEquals to be opCmp()==0 is a no-go, then I'd much
rather say it should be a compile error if the user defines
opCmp but
not opEquals.
No. There is this notion of partial ordering that makes
objects not smaller and not greater than others, yet not
equal. --
I would strongly argue that if lhs.opCmp(rhs) == 0 is not
equivalent to lhs == rhs, then it that type is broken and
should not be using opCmp to do its comparisons.
std.algorithm.sort allows you to use any predicate you want,
allowing for such orderings, but it does not work with generic
code for a type to define opCmp or opEquals such that they're
not consistent, because that's not consistent with how
comparisons work for the built-in types.
- Jonathan M Davis
floating point ?
When it comes to equality and comparison, floating point values
are mess that I would really hope no one would be looking to
emulate with their own types. I grant you that they're a built in
type, but they do not have clean semantics (particularly with
regards to equality). IMHO, user-defined types should emulate
integers with regards to how the comparison operators work.
Allowing more nonsense like what FP does does not improve things.

- Jonathan M Davis
Andrei Alexandrescu via Digitalmars-d
2014-07-23 18:48:42 UTC
Permalink
Post by H. S. Teoh via Digitalmars-d
Why isn't "a==b" rewritten as "a.opCmp(b)==0"?? I'm pretty sure TDPL
says this is the case (unfortunately I'm at work so I can't check my
copy of TDPL).
https://issues.dlang.org/show_bug.cgi?id=13179
:-(
It's a good decision. There are types that are comparable for equality
but not compared for ordering. -- Andrei
H. S. Teoh via Digitalmars-d
2014-07-23 18:52:17 UTC
Permalink
Post by Andrei Alexandrescu via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
Why isn't "a==b" rewritten as "a.opCmp(b)==0"?? I'm pretty sure TDPL
says this is the case (unfortunately I'm at work so I can't check my
copy of TDPL).
https://issues.dlang.org/show_bug.cgi?id=13179
:-(
It's a good decision. There are types that are comparable for equality
but not compared for ordering. -- Andrei
That's the wrong way round. I fully agree that we should not
autogenerate opCmp if the user defines opEquals, since not all types
comparable with equality are orderable. However, surely all orderable
types are equality-comparable! Therefore, if opCmp is defined but
opEquals isn't, then we should autogenerate opEquals to be the same as
a.opCmp(b)==0.


T
--
No! I'm not in denial!
Andrei Alexandrescu via Digitalmars-d
2014-07-23 21:35:03 UTC
Permalink
Post by H. S. Teoh via Digitalmars-d
Post by Andrei Alexandrescu via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
Why isn't "a==b" rewritten as "a.opCmp(b)==0"?? I'm pretty sure TDPL
says this is the case (unfortunately I'm at work so I can't check my
copy of TDPL).
https://issues.dlang.org/show_bug.cgi?id=13179
:-(
It's a good decision. There are types that are comparable for equality
but not compared for ordering. -- Andrei
That's the wrong way round.
No.
Post by H. S. Teoh via Digitalmars-d
I fully agree that we should not
autogenerate opCmp if the user defines opEquals, since not all types
comparable with equality are orderable. However, surely all orderable
types are equality-comparable!
http://en.wikipedia.org/wiki/Lattice_(order)
Post by H. S. Teoh via Digitalmars-d
Therefore, if opCmp is defined but
opEquals isn't, then we should autogenerate opEquals to be the same as
a.opCmp(b)==0.
It's a sensible decision, but I'm not so sure.


Andrei
H. S. Teoh via Digitalmars-d
2014-07-23 22:39:44 UTC
Permalink
[...]
Post by Andrei Alexandrescu via Digitalmars-d
I fully agree that we should not autogenerate opCmp if the user
defines opEquals, since not all types comparable with equality are
orderable. However, surely all orderable types are
equality-comparable!
http://en.wikipedia.org/wiki/Lattice_(order)
[...]

And why should this be the default behaviour? The <, <=, >=, > operators
imply linear ordering, not general partial order. If you really want to
implement a non-linear partial ordering, you can always define both
opCmp and opEquals. This should be the *non*-default case, since in the
vast majority of cases, defining opCmp means you want a linear ordering.
Linear ordering should be default, and partial ordering possible if the
programmer explicitly asks for it (by implementing opEquals manually).


T
--
Winners never quit, quitters never win. But those who never quit AND never win are idiots.
Andrei Alexandrescu via Digitalmars-d
2014-07-23 23:52:10 UTC
Permalink
Post by H. S. Teoh via Digitalmars-d
[...]
Post by Andrei Alexandrescu via Digitalmars-d
I fully agree that we should not autogenerate opCmp if the user
defines opEquals, since not all types comparable with equality are
orderable. However, surely all orderable types are
equality-comparable!
http://en.wikipedia.org/wiki/Lattice_(order)
[...]
And why should this be the default behaviour? The <, <=, >=, > operators
imply linear ordering, not general partial order. If you really want to
implement a non-linear partial ordering, you can always define both
opCmp and opEquals. This should be the *non*-default case, since in the
vast majority of cases, defining opCmp means you want a linear ordering.
Linear ordering should be default, and partial ordering possible if the
programmer explicitly asks for it (by implementing opEquals manually).
I'm unconvinced. Most algorithms that need inequality don't need
equality comparison; instead, they consider objects for which both !(a <
b) && !(b < a) in the same "equivalence class" that doesn't assume they
are actually equal.

Bottom line, inferring opEquals from opCmp seems fishy.


Andrei
deadalnix via Digitalmars-d
2014-07-24 01:34:05 UTC
Permalink
On Wednesday, 23 July 2014 at 23:52:01 UTC, Andrei Alexandrescu
Post by Andrei Alexandrescu via Digitalmars-d
On Wed, Jul 23, 2014 at 02:35:03PM -0700, Andrei Alexandrescu
[...]
Post by Andrei Alexandrescu via Digitalmars-d
I fully agree that we should not autogenerate opCmp if the
user
defines opEquals, since not all types comparable with
equality are
orderable. However, surely all orderable types are
equality-comparable!
http://en.wikipedia.org/wiki/Lattice_(order)
[...]
And why should this be the default behaviour? The <, <=, >=, > operators
imply linear ordering, not general partial order. If you
really want to
implement a non-linear partial ordering, you can always define both
opCmp and opEquals. This should be the *non*-default case,
since in the
vast majority of cases, defining opCmp means you want a linear ordering.
Linear ordering should be default, and partial ordering
possible if the
programmer explicitly asks for it (by implementing opEquals
manually).
I'm unconvinced. Most algorithms that need inequality don't
need equality comparison; instead, they consider objects for
which both !(a < b) && !(b < a) in the same "equivalence class"
that doesn't assume they are actually equal.
Bottom line, inferring opEquals from opCmp seems fishy.
Andrei
NaN is a good example.
Daniel Gibson via Digitalmars-d
2014-07-24 08:18:21 UTC
Permalink
Post by Andrei Alexandrescu via Digitalmars-d
I'm unconvinced. Most algorithms that need inequality don't need
equality comparison; instead, they consider objects for which both !(a <
b) && !(b < a) in the same "equivalence class" that doesn't assume they
are actually equal.
Bottom line, inferring opEquals from opCmp seems fishy.
You're thinking too algorithm-centric :-P

When I implement the "comparison operator" for my type, I expect it to
be used for comparisons - and that includes equality.
If I had the feeling that I could implement == in a more efficient way,
or that I actually want equality to have different semantics, I'd just
implement opEquals as well.

IMHO, everything else would be just confusing to the "average" user, and
if someone wants to be confused by counterintuitive rules (however much
sense they may make in some way) he could as well just use C++ instead.

But if the general view really is that opEquals should *not* be opCmp ==
0 by default, for performance reasons or whatever, then please enforce
defining opEquals when opCmd is defined, so it's at least explicit that
opCmd does not define equality.

Cheers,
Daniel
via Digitalmars-d
2014-07-25 18:04:09 UTC
Permalink
Post by Daniel Gibson via Digitalmars-d
When I implement the "comparison operator" for my type, I
expect it to be used for comparisons - and that includes
equality.
If I had the feeling that I could implement == in a more
efficient way, or that I actually want equality to have
different semantics, I'd just implement opEquals as well.
IMHO, everything else would be just confusing to the "average"
user, and if someone wants to be confused by counterintuitive
rules (however much sense they may make in some way) he could
as well just use C++ instead.
But if the general view really is that opEquals should *not* be
opCmp == 0 by default, for performance reasons or whatever,
then please enforce defining opEquals when opCmd is defined, so
it's at least explicit that opCmd does not define equality.
+1

Silently breaking (IMHO reasonable) expectations is bad.
John Colvin via Digitalmars-d
2014-07-23 22:19:47 UTC
Permalink
On Wednesday, 23 July 2014 at 18:53:57 UTC, H. S. Teoh via
However, surely all orderable types are equality-comparable!
just because 2 objects don't have a defined ordering between them
doesn't mean they are equal in a more general sense.

Yes it's a gotcha but I think it's a worthwhile one.
via Digitalmars-d
2014-07-23 22:42:19 UTC
Permalink
On Wednesday, 23 July 2014 at 18:53:57 UTC, H. S. Teoh via
Post by H. S. Teoh via Digitalmars-d
That's the wrong way round. I fully agree that we should not
autogenerate opCmp if the user defines opEquals, since not all
types
comparable with equality are orderable. However, surely all
orderable
types are equality-comparable! Therefore, if opCmp is defined
but
opEquals isn't, then we should autogenerate opEquals to be the
same as
a.opCmp(b)==0.
You can define an order for sets/intervals without equality...
For fuzzy numbers it gets even worse. You can define it such that
a<b and b>a both are true...
H. S. Teoh via Digitalmars-d
2014-07-23 23:01:08 UTC
Permalink
On Wednesday, 23 July 2014 at 18:53:57 UTC, H. S. Teoh via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
That's the wrong way round. I fully agree that we should not
autogenerate opCmp if the user defines opEquals, since not all types
comparable with equality are orderable. However, surely all
orderable types are equality-comparable! Therefore, if opCmp is
defined but opEquals isn't, then we should autogenerate opEquals to
be the same as a.opCmp(b)==0.
You can define an order for sets/intervals without equality... For
fuzzy numbers it gets even worse. You can define it such that a<b and
b>a both are true...
(a<b && b>a) is true for ints.


T
--
All men are mortal. Socrates is mortal. Therefore all men are Socrates.
via Digitalmars-d
2014-07-24 05:02:37 UTC
Permalink
On Wednesday, 23 July 2014 at 23:02:48 UTC, H. S. Teoh via
Post by H. S. Teoh via Digitalmars-d
Post by via Digitalmars-d
fuzzy numbers it gets even worse. You can define it such that
a<b and
b>a both are true...
(a<b && b>a) is true for ints.
That was a typo, for fuzzy numbers you can define less than such
that a<b and b>a both are true.

Fuzzy(-1,0,1) vs Fuzzy(-2,0,2)
Daniel Murphy via Digitalmars-d
2014-07-24 06:18:53 UTC
Permalink
"Ola Fosheim GrÞstad" " wrote in message
On Wednesday, 23 July 2014 at 23:02:48 UTC, H. S. Teoh via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
fuzzy numbers it gets even worse. You can define it such that a<b and
b>a both are true...
(a<b && b>a) is true for ints.
That was a typo, for fuzzy numbers you can define less than such that a<b
and b>a both are true.
Fuzzy(-1,0,1) vs Fuzzy(-2,0,2)
a<b and b>a can be true for ints.
via Digitalmars-d
2014-07-24 06:36:03 UTC
Permalink
Post by Daniel Murphy via Digitalmars-d
"Ola Fosheim GrÞstad" " wrote in message
Post by via Digitalmars-d
On Wednesday, 23 July 2014 at 23:02:48 UTC, H. S. Teoh via
Post by H. S. Teoh via Digitalmars-d
Post by via Digitalmars-d
fuzzy numbers it gets even worse. You can define it such
that a<b and
b>a both are true...
(a<b && b>a) is true for ints.
That was a typo, for fuzzy numbers you can define less than
such that a<b and b>a both are true.
Fuzzy(-1,0,1) vs Fuzzy(-2,0,2)
a<b and b>a can be true for ints.
So I keep making the same typo :P...

For fuzzy numbers you can define less than such that a<b and b<a
both are true... yes?
Daniel Murphy via Digitalmars-d
2014-07-24 06:46:27 UTC
Permalink
"Ola Fosheim GrÞstad" " wrote in message
For fuzzy numbers you can define less than such that a<b and b<a both are
true... yes?
You could, but if you do it with opCmp it looks like operator overloading
abuse to me.
via Digitalmars-d
2014-07-24 09:15:55 UTC
Permalink
Post by Daniel Murphy via Digitalmars-d
"Ola Fosheim GrÞstad" " wrote in message
Post by via Digitalmars-d
For fuzzy numbers you can define less than such that a<b and
b<a both are true... yes?
You could, but if you do it with opCmp it looks like operator
overloading abuse to me.
Well, but FuzzyNumbers are fuzzy sets that are treated like
scalars in a pragmatic, but imperfect way. It makes sense to
state that a vivid design A is both uglier and prettier than a
boring and dull design B.

I think opCmp is a mistake once you move beyond real scalars.
Defining sort order is a separate "tool". Take for instance
complex numbers that can be ordered by magnitude, but you need to
account for phase (in some arbitrary way since it is circular) to
get total order. That does not mean that one should use the
sort-comparison for non-sort comparison of complex numbers.
Jonathan M Davis via Digitalmars-d
2014-07-24 00:41:01 UTC
Permalink
On Wednesday, 23 July 2014 at 18:53:57 UTC, H. S. Teoh via
On Wed, Jul 23, 2014 at 11:48:42AM -0700, Andrei Alexandrescu
Post by Andrei Alexandrescu via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
Why isn't "a==b" rewritten as "a.opCmp(b)==0"?? I'm pretty
sure TDPL
says this is the case (unfortunately I'm at work so I can't
check my
copy of TDPL).
https://issues.dlang.org/show_bug.cgi?id=13179
:-(
It's a good decision. There are types that are comparable for
equality
but not compared for ordering. -- Andrei
That's the wrong way round. I fully agree that we should not
autogenerate opCmp if the user defines opEquals, since not all
types
comparable with equality are orderable. However, surely all
orderable
types are equality-comparable! Therefore, if opCmp is defined
but
opEquals isn't, then we should autogenerate opEquals to be the
same as
a.opCmp(b)==0.
That would incur a silent performance hit. We should either force
the programmer to define opEquals (even if they just make it
return a.opCmp(b) == 0) or we should keep the normal, generated
one.

The best option though would be to provide some way for the
programmer to tell the compiler that they want to use the default
one so that they still have to declare opEquals when they define
opCmp (to make sure that the programmer didn't forget it), but
they're still able to use the built-in one rather than writing it
themselves. IIRC, C++11 has a way of doing that. Maybe we should
add something similar.

- Jonathan M Davis
Daniel Murphy via Digitalmars-d
2014-07-24 06:20:01 UTC
Permalink
"Jonathan M Davis" wrote in message
The best option though would be to provide some way for the programmer to
tell the compiler that they want to use the default one so that they still
have to declare opEquals when they define opCmp (to make sure that the
programmer didn't forget it), but they're still able to use the built-in
one rather than writing it themselves. IIRC, C++11 has a way of doing
that. Maybe we should add something similar.
bool opEquals(const ref other) const { return this.tupleof ==
other.tupleof; }
Jacob Carlborg via Digitalmars-d
2014-07-24 06:29:23 UTC
Permalink
Post by Jonathan M Davis via Digitalmars-d
That would incur a silent performance hit.
So does default initialized values, virtual by default, classes
allocated on the heap and other features of D. By default D chooses
safety and correctness. If the programmer needs more performance some
additional code might be required.
--
/Jacob Carlborg
Brian Schott via Digitalmars-d
2014-07-23 21:14:34 UTC
Permalink
As of about 2 minutes ago, D-Scanner can help with this.

---
struct A {
int opCmp() const;
}
---

$ ./dscanner --styleCheck ~/tmp/test.d
test.d(1:8)[warn]: 'A' has method 'opCmp', but not 'opEquals'.
Jonathan M Davis via Digitalmars-d
2014-07-24 00:31:54 UTC
Permalink
On Wednesday, 23 July 2014 at 16:47:40 UTC, H. S. Teoh via
Post by H. S. Teoh via Digitalmars-d
----
struct S {
int x;
int y;
int opCmp(S s) {
return x - s.x; // compare only x
}
}
void main() {
auto s1 = S(1,2);
auto s2 = S(1,3);
auto s3 = S(2,1);
assert(s1 < s3); // OK
assert(s2 < s3); // OK
assert(s3 > s1); // OK
assert(s3 > s2); // OK
assert(s1 <= s2 && s2 >= s1); // OK
assert(s1 == s2); // FAIL -- WAT??
}
----
The reason for this is that the <, <=, >=, > operators are
defined in
terms of opCmp (which, btw, is defined to return 0 when the
objects
being compared are equal), but == is defined in terms of
opEquals. When
opEquals is not defined, it defaults to the built-in compiler
definition, which is a membership equality test, even if opCmp
*is*
defined, and returns 0 when the objects are equal.
Why isn't "a==b" rewritten as "a.opCmp(b)==0"?? I'm pretty sure
TDPL
says this is the case (unfortunately I'm at work so I can't
check my
copy of TDPL).
https://issues.dlang.org/show_bug.cgi?id=13179
:-(
I would argue that the compiler should still be generating
opEquals even if opCmp is defined. Otherwise, even if opCmp is
consistent with the built-in opEquals, you'll be forced to
reimplement opEquals - and toHash if you're using that type as a
key, since once you define opEquals, you have to define toHash.

If it makes sense for a type to define opCmp but not define
opEquals (which I seriously question), then I think that it
should be explicit, in which case, we can use @disable, e.g.
something like

struct S
{
@disable bool opEquals(ref S s);

int opCmp(ref S S)
{
...
}

...
}

- Jonathan M Davis
Jonathan M Davis via Digitalmars-d
2014-07-24 00:51:31 UTC
Permalink
Post by Jonathan M Davis via Digitalmars-d
I would argue that the compiler should still be generating
opEquals even if opCmp is defined.
I take this back. As I later suggested in a post somewhere else
in this thread (and the bug report that H.S. Teoh opened), I
think that we should continue to not define opEquals, but we
should add a way to tell the compiler to use the
default-generated one (similar to what C++11 does). That way, the
programmer is forced to consider what opEquals is supposed to do
when opCmp is defined, but they're still able to use the
default-generated one. The same goes for toHash.

Regardless, because automatically making opEquals be
lhs.opCmp(rhs) == 0 incurs a silent performance hit, I think that
it's a bad idea.

- Jonathan M Davis
H. S. Teoh via Digitalmars-d
2014-07-24 01:37:22 UTC
Permalink
I would argue that the compiler should still be generating opEquals
even if opCmp is defined.
I take this back. As I later suggested in a post somewhere else in
this thread (and the bug report that H.S. Teoh opened), I think that
we should continue to not define opEquals, but we should add a way to
tell the compiler to use the default-generated one (similar to what
C++11 does). That way, the programmer is forced to consider what
opEquals is supposed to do when opCmp is defined, but they're still
able to use the default-generated one. The same goes for toHash.
Regardless, because automatically making opEquals be lhs.opCmp(rhs) ==
0 incurs a silent performance hit, I think that it's a bad idea.
[...]

This sounds like a reasonable compromise. If the user defines opCmp,
then it is an error not to define opEquals. However, opEquals can be
specified to be default, to get the compiler-generated version:

struct S {
int opCmp(S s) { ... }
bool opEquals(S s) = default;
}

Optionally, we could also allow opEquals to be @disabled, perhaps.

Same goes with toHash.

Keep in mind, though, that due to current AA changes in 2.066 beta,
existing code WILL break unless we autogenerate opEquals to be
opCmp()=0. In fact, issue 13179 was originally filed because 2.066 beta
broke Tango. My current stance is that these AA changes are an
improvement that we should keep, so then the question becomes, should we
break code over it, or should we introduce opEquals = (opCmp()==0),
which would allow existing code *not* to break?

Given the choice between (1) breaking code *and* allowing opCmp to be
inconsistent with opEquals, as the current situation is, and (2) *not*
breaking code and making opEquals consistent with opCmp by default, I
would choose (2) as being clearly more advantageous. The above
compromise solves the opEquals/opCmp consistency problem, but does not
address the inevitable code breakage that will happen when 2.066 is
released. Is it really worth the ire of D coders to have their existing
code break, for the questionable benefit of being able to make opEquals
inconsistent with opCmp just so we can support partial orderings on
types?

I don't know about you, but if it were up to me, I would much rather go
with the solution of setting opEquals = (opCmp()==0) by default, and let
the user redefine opEquals if they want partial orderings or eliminate
performance hits, etc..


T
--
Skill without imagination is craftsmanship and gives us many useful objects such as wickerwork picnic baskets. Imagination without skill gives us modern art. -- Tom Stoppard
Dicebot via Digitalmars-d
2014-07-24 10:11:37 UTC
Permalink
On Thursday, 24 July 2014 at 01:39:01 UTC, H. S. Teoh via
Post by H. S. Teoh via Digitalmars-d
Keep in mind, though, that due to current AA changes in 2.066
beta,
existing code WILL break unless we autogenerate opEquals to be
opCmp()=0. In fact, issue 13179 was originally filed because
2.066 beta
broke Tango.
This is exactly what I was referring to by "you will answer to
user complaints". In context of declared backwards compatibility
efforts decision to not use pragmatical solution in favor of
purist approach that brakes user code does sound strange.
w0rp via Digitalmars-d
2014-07-24 11:22:45 UTC
Permalink
I wonder. If opCmp is supposed to imply partial ordering, then
that means opCmp should imply the antisymmetric property of
partial ordering. http://mathworld.wolfram.com/PartialOrder.html

a <= b and b <= a implies a = b.

That would mean for us that opEquals being generated with opCmp
== 0 would make sense.

Without that transitive property, it would imply only that it was
a preorder, but not a partial order.
http://mathworld.wolfram.com/Preorder.html
Jonathan M Davis via Digitalmars-d
2014-07-24 18:30:26 UTC
Permalink
On Thursday, 24 July 2014 at 01:39:01 UTC, H. S. Teoh via
Post by H. S. Teoh via Digitalmars-d
Keep in mind, though, that due to current AA changes in 2.066
beta,
existing code WILL break unless we autogenerate opEquals to be
opCmp()=0. In fact, issue 13179 was originally filed because
2.066 beta
broke Tango. My current stance is that these AA changes are an
improvement that we should keep, so then the question becomes,
should we
break code over it, or should we introduce opEquals =
(opCmp()==0),
which would allow existing code *not* to break?
Can we just adjust the AA implementation so that it uses
lhs.opCmp(rhs) == 0 if opEquals isn't defined and produces a
deprecation warning about that? That way, we avoid immediately
breaking folks, but we still move towards requiring that they
define opEquals.

- Jonathan M Davis
Andrei Alexandrescu via Digitalmars-d
2014-07-24 19:35:21 UTC
Permalink
On Thursday, 24 July 2014 at 01:39:01 UTC, H. S. Teoh via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
Keep in mind, though, that due to current AA changes in 2.066 beta,
existing code WILL break unless we autogenerate opEquals to be
opCmp()=0. In fact, issue 13179 was originally filed because 2.066 beta
broke Tango. My current stance is that these AA changes are an
improvement that we should keep, so then the question becomes, should we
break code over it, or should we introduce opEquals = (opCmp()==0),
which would allow existing code *not* to break?
Can we just adjust the AA implementation so that it uses lhs.opCmp(rhs)
== 0 if opEquals isn't defined and produces a deprecation warning about
that? That way, we avoid immediately breaking folks, but we still move
towards requiring that they define opEquals.
I like Daniel's idea to auto-define opEquals as a field-for-field
comparison. -- Andrei
Jonathan M Davis via Digitalmars-d
2014-07-24 20:36:02 UTC
Permalink
On Thursday, 24 July 2014 at 19:35:12 UTC, Andrei Alexandrescu
Post by Andrei Alexandrescu via Digitalmars-d
I like Daniel's idea to auto-define opEquals as a
field-for-field comparison. -- Andrei
That's basically what the compiler-generated opEquals does
(though I think that it'll just to a memcmp if it knows that it
can get away with that). So, if that's what you want, you're
arguing for just have the compiler still define opEquals for you
even if opCmp is defined. And I'm fine with that, but if the
concern is code breakage for AAs, and opCmp is not defined in a
way that's consistent with opEquals, then that would break them.
Now, I think that such types are buggy, so I'm not sure that
that's all that big a deal, but if I understand correctly,
basically anything that we do with 2.066 which doesn't involve
continuing to use lhs.opCmp(rhs) == 0 for the AAs will break
them, and we need to change it to use opEquals, so I'm not sure
that we _can_ avoid code breakage unless the type defines
opEquals, and it defines it in a manner which is consistent with
opCmp (which it _should_ do but might not). And if that's the
case, then it just comes down to what type of code breakage we
want to incur. And IMHO, having the default opEquals continue to
be generated when opCmp is defined is a very workable solution,
since no types should have defined opCmp in a way that was
inconsistent with opEquals.

- Jonathan M Davis
H. S. Teoh via Digitalmars-d
2014-07-24 21:52:19 UTC
Permalink
Post by Andrei Alexandrescu via Digitalmars-d
I like Daniel's idea to auto-define opEquals as a field-for-field
comparison. -- Andrei
Isn't that what the compiler already does?
That's basically what the compiler-generated opEquals does (though I
think that it'll just to a memcmp if it knows that it can get away
with that). So, if that's what you want, you're arguing for just have
the compiler still define opEquals for you even if opCmp is defined.
And I'm fine with that, but if the concern is code breakage for AAs,
and opCmp is not defined in a way that's consistent with opEquals,
then that would break them. Now, I think that such types are buggy, so
I'm not sure that that's all that big a deal, but if I understand
correctly, basically anything that we do with 2.066 which doesn't
involve continuing to use lhs.opCmp(rhs) == 0 for the AAs will break
them, and we need to change it to use opEquals, so I'm not sure that
we _can_ avoid code breakage unless the type defines opEquals, and it
defines it in a manner which is consistent with opCmp (which it
_should_ do but might not). And if that's the case, then it just comes
down to what type of code breakage we want to incur. And IMHO, having
the default opEquals continue to be generated when opCmp is defined is
a very workable solution, since no types should have defined opCmp in
a way that was inconsistent with opEquals.
[...]

Keep in mind that the last sentence means that wrong code (i.e.
inconsistent opCmp/opEquals) will silently compile and misbehave at
runtime.


T
--
Answer: Because it breaks the logical sequence of discussion.
Question: Why is top posting bad?
Jonathan M Davis via Digitalmars-d
2014-07-25 01:16:19 UTC
Permalink
On Thursday, 24 July 2014 at 21:54:01 UTC, H. S. Teoh via
On Thu, Jul 24, 2014 at 08:36:02PM +0000, Jonathan M Davis via
And IMHO, having the default opEquals continue to be generated
when
opCmp is defined is a very workable solution, since no types
should
have defined opCmp in
a way that was inconsistent with opEquals.
[...]
Keep in mind that the last sentence means that wrong code (i.e.
inconsistent opCmp/opEquals) will silently compile and
misbehave at
runtime.
Well, that's exactly the behavior in 2.065 from what I can tell.
If you don't define opEquals, the compiler defines it for you
even if you defined opCmp. And trying out git head, it does
exactly the same thing. You only get a compilation error when
using AAs, which is pretty weird IMHO, and it seems very wrong to
suddenly require that opEquals be defined when the default is
still being generated. The only way that anyone is going to have
problems is if their opCmp is not consistent with opEquals, which
is just plain a bug IMHO, so if switching the AAs to opEquals
instead of opCmp causes bugs, you're just swapping one set of
bugs for another, which seems fine to me. Certainly, I think that
it's stupid to require that opEquals be defined just because
you're using the type as an AA key when it's not required
otherwise.

- Jonathan M Davis
Walter Bright via Digitalmars-d
2014-07-25 04:50:37 UTC
Permalink
Post by H. S. Teoh via Digitalmars-d
https://issues.dlang.org/show_bug.cgi?id=13179
Consider also:

http://www.reddit.com/r/programming/comments/2bl51j/programming_in_d_a_great_online_book_for_learning/cj75gm9

The current scheme breaks existing code - code that was formerly correct and
working.

AAs don't make sense if the notion of == on members is invalid. AAs formerly
required opCmp, and yes, opCmp could be constructed to give different results
for opCmp==0 than ==, but I would expect such an object to not be used in an AA,
again because it doesn't make sense.

Using the default generated opEquals for AAs may break code, such as the an AA
of the structs in the parent post, but it seems unlikely that that code was
correct anyway in an AA (as it would give erratic results).

Kenji's rebuttal https://issues.dlang.org/show_bug.cgi?id=13179#c2 is probably
the best counter-argument, and I'd go with it if it didn't result in code breakage.
Manu via Digitalmars-d
2014-07-25 05:52:46 UTC
Permalink
On 25 July 2014 14:50, Walter Bright via Digitalmars-d <
Post by Walter Bright via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
https://issues.dlang.org/show_bug.cgi?id=13179
http://www.reddit.com/r/programming/comments/2bl51j/
programming_in_d_a_great_online_book_for_learning/cj75gm9
The current scheme breaks existing code - code that was formerly correct
and working.
AAs don't make sense if the notion of == on members is invalid. AAs
formerly required opCmp, and yes, opCmp could be constructed to give
different results for opCmp==0 than ==, but I would expect such an object
to not be used in an AA, again because it doesn't make sense.
Using the default generated opEquals for AAs may break code, such as the
an AA of the structs in the parent post, but it seems unlikely that that
code was correct anyway in an AA (as it would give erratic results).
Kenji's rebuttal https://issues.dlang.org/show_bug.cgi?id=13179#c2 is
probably the best counter-argument, and I'd go with it if it didn't result
in code breakage.
I don't really see how opCmp == 0 could be unreliable or unintended. It was
deliberately written by the author, so definitely not unintended, and I
can't imagine anybody would ever deliberately ignore the == 0 case when
implementing an opCmp, or produce logic that works for less or greater, but
fails for equal.
<= and >= are expressed by opCmp, which imply that testing for equality
definitely works as the user intended.

In lieu of an opEquals, how can a deliberately implemented opCmp, which we
know works in the == case (otherwise <= or >= wouldn't work either) ever be
a worse choice than an implicitly generated opEquals?

Personally, just skimming through this thread, I find it baffling that this
is controversial.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20140725/92df295d/attachment.html>
Jonathan M Davis via Digitalmars-d
2014-07-25 06:31:39 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
https://issues.dlang.org/show_bug.cgi?id=13179
http://www.reddit.com/r/programming/comments/2bl51j/programming_in_d_a_great_online_book_for_learning/cj75gm9
The current scheme breaks existing code - code that was
formerly correct and working.
AAs don't make sense if the notion of == on members is invalid.
AAs formerly required opCmp, and yes, opCmp could be
constructed to give different results for opCmp==0 than ==, but
I would expect such an object to not be used in an AA, again
because it doesn't make sense.
Using the default generated opEquals for AAs may break code,
such as the an AA of the structs in the parent post, but it
seems unlikely that that code was correct anyway in an AA (as
it would give erratic results).
Exactly. The only reason that switching from using lhs.opCmp(rhs)
== 0 to opEquals would break code is if a type does not define
them such that they're equivalent, which would mean that opEquals
and/or opCmp was defined in a buggy manner. So, the only way that
the change would break code is if it was broken in the first
place. All it risks is making it so that the bug exhibits itself
in an additional case.
Post by Walter Bright via Digitalmars-d
Kenji's rebuttal
https://issues.dlang.org/show_bug.cgi?id=13179#c2 is probably
the best counter-argument, and I'd go with it if it didn't
result in code breakage.
Yeah. It wouldn't be all that bad to do something similar to
C++11 and make it so that we explicitly indicate when we want to
use the default opEquals (or toHash), but doing so would break
code, and outright just making it so that opEquals must be
defined when AAs are used without allowing a way for the
compiler-generated opEquals or toHash to be used seems very
broken to me. With such a change, _no_ existing user-defined type
would be able to use the built-in opEquals or toHash functions if
they're used with AAs, which is particularly bad in the case of
toHash, since that's much harder to get right.

- Jonathan M Davis
Jacob Carlborg via Digitalmars-d
2014-07-25 08:28:53 UTC
Permalink
Exactly. The only reason that switching from using lhs.opCmp(rhs) == 0
to opEquals would break code is if a type does not define them such that
they're equivalent, which would mean that opEquals and/or opCmp was
defined in a buggy manner. So, the only way that the change would break
code is if it was broken in the first place. All it risks is making it
so that the bug exhibits itself in an additional case.
If the type is only used as an AA key and never checked for equivalent
it worked when opCmp as used for AA keys.
--
/Jacob Carlborg
Walter Bright via Digitalmars-d
2014-07-25 08:55:18 UTC
Permalink
If the type is only used as an AA key and never checked for equivalent it worked
when opCmp as used for AA keys.
Then we'll just get another bug report from AAs behaving differently from ==.
Jacob Carlborg via Digitalmars-d
2014-07-25 09:04:50 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Then we'll just get another bug report from AAs behaving differently from ==.
No, not as long as it's not used.
--
/Jacob Carlborg
Walter Bright via Digitalmars-d
2014-07-25 09:35:13 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
Post by Walter Bright via Digitalmars-d
Then we'll just get another bug report from AAs behaving differently from ==.
No, not as long as it's not used.
Well, that's a forlorn hope :-)
Ary Borenszweig via Digitalmars-d
2014-07-25 12:10:40 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
https://issues.dlang.org/show_bug.cgi?id=13179
http://www.reddit.com/r/programming/comments/2bl51j/programming_in_d_a_great_online_book_for_learning/cj75gm9
The current scheme breaks existing code - code that was formerly
correct and working.
AAs don't make sense if the notion of == on members is invalid. AAs
formerly required opCmp, and yes, opCmp could be constructed to give
different results for opCmp==0 than ==, but I would expect such an
object to not be used in an AA, again because it doesn't make sense.
Using the default generated opEquals for AAs may break code, such as
the an AA of the structs in the parent post, but it seems unlikely
that that code was correct anyway in an AA (as it would give erratic
results).
Exactly. The only reason that switching from using lhs.opCmp(rhs) == 0
to opEquals would break code is if a type does not define them such that
they're equivalent, which would mean that opEquals and/or opCmp was
defined in a buggy manner. So, the only way that the change would break
code is if it was broken in the first place. All it risks is making it
so that the bug exhibits itself in an additional case.
Not at all.

If you have a type that has partial ordering (only cares about opCmp,
not about opEquals), but still keeps the default opEquals, then this
would silently break someone's code by changing their opEquals semantic.

THIS is the breaking change.
Jonathan M Davis via Digitalmars-d
2014-07-25 06:44:12 UTC
Permalink
On Friday, 25 July 2014 at 05:52:57 UTC, Manu via Digitalmars-d
Post by Manu via Digitalmars-d
On 25 July 2014 14:50, Walter Bright via Digitalmars-d <
Post by Walter Bright via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
https://issues.dlang.org/show_bug.cgi?id=13179
http://www.reddit.com/r/programming/comments/2bl51j/
programming_in_d_a_great_online_book_for_learning/cj75gm9
The current scheme breaks existing code - code that was
formerly correct
and working.
AAs don't make sense if the notion of == on members is
invalid. AAs
formerly required opCmp, and yes, opCmp could be constructed
to give
different results for opCmp==0 than ==, but I would expect
such an object
to not be used in an AA, again because it doesn't make sense.
Using the default generated opEquals for AAs may break code,
such as the
an AA of the structs in the parent post, but it seems unlikely
that that
code was correct anyway in an AA (as it would give erratic
results).
Kenji's rebuttal
https://issues.dlang.org/show_bug.cgi?id=13179#c2 is
probably the best counter-argument, and I'd go with it if it
didn't result
in code breakage.
I don't really see how opCmp == 0 could be unreliable or
unintended. It was
deliberately written by the author, so definitely not
unintended, and I
can't imagine anybody would ever deliberately ignore the == 0
case when
implementing an opCmp, or produce logic that works for less or
greater, but
fails for equal.
<= and >= are expressed by opCmp, which imply that testing for
equality
definitely works as the user intended.
In lieu of an opEquals, how can a deliberately implemented
opCmp, which we
know works in the == case (otherwise <= or >= wouldn't work
either) ever be
a worse choice than an implicitly generated opEquals?
Personally, just skimming through this thread, I find it
baffling that this
is controversial.
So, in the case where opCmp was defined but not opEquals, instead
of using the normal, built-in opEquals (which should already be
equivalent to lhs.opCmp(rhs) == 0), we're going to make the
compiler generate opEquals as lhs.opCmp(rhs) == 0? That's a
silent performance hit for no good reason IMHO. It doesn't even
improve correctness except in the cases where the programmer
should have been defining opEquals in the first place, because
lhs.opCmp(rhs) == 0 wasn't equivalent to the compiler-generate
opEquals. So, we'd be making good code worse just to try and fix
an existing bug in bad code in order to do what? Not break the
already broken code?

I can understand wanting to avoid breaking code when changing
from using opCmp to using opEquals with AAs, but it's only an
issue if the code was already broken by defining opCmp in a way
that didn't match opEquals, so if I find it odd that any part of
this is controversial, it's the fact that anyone thinks that we
should try and avoid breaking code where opEquals and opCmp
weren't equivalent.

- Jonathan M Davis
Jacob Carlborg via Digitalmars-d
2014-07-25 08:21:25 UTC
Permalink
So, in the case where opCmp was defined but not opEquals, instead of
using the normal, built-in opEquals (which should already be equivalent
to lhs.opCmp(rhs) == 0), we're going to make the compiler generate
opEquals as lhs.opCmp(rhs) == 0? That's a silent performance hit for no
good reason IMHO.
So are default initialized variables, virtual by default and other
similar cases. D aims for correctness and safety first. With the option
to get better performance by, possibly, writing some extra code.
It doesn't even improve correctness except in the
cases where the programmer should have been defining opEquals in the
first place, because lhs.opCmp(rhs) == 0 wasn't equivalent to the
compiler-generate opEquals. So, we'd be making good code worse just to
try and fix an existing bug in bad code in order to do what? Not break
the already broken code?
I don't understand this. How are we making good code worse? If the code
was working previously, opCmp == 0 should have had the same result as
the default generated opEquals. In that case it's perfectly safe to
define opEquals to be opCmp == 0.
I can understand wanting to avoid breaking code when changing from using
opCmp to using opEquals with AAs, but it's only an issue if the code was
already broken by defining opCmp in a way that didn't match opEquals, so
if I find it odd that any part of this is controversial, it's the fact
that anyone thinks that we should try and avoid breaking code where
opEquals and opCmp weren't equivalent.
By defining opEquals to be opCmp == 0 we're:

1. We're not breaking code where it wasn't broken previously
2. We're fixing broken code. That is when opEqual and opCmp == 0 gave
different results
--
/Jacob Carlborg
Jonathan M Davis via Digitalmars-d
2014-07-25 09:46:55 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
1. We're not breaking code where it wasn't broken previously
2. We're fixing broken code. That is when opEqual and opCmp ==
0 gave different results
Code that worked perfectly fine before is now slower, because
it's using opCmp for opEquals when it wasn't before. Even worse,
if you define opEquals, you're then forced to define toHash,
which is much harder to get right. So, in order to avoid a
performance hit on opEquals from defining opCmp, you now have to
define toHash, which significantly increases the chances of bugs.
And regardless of the increased risk of bugs, it's extra code
that you shouldn't need to write anyway, because the normal,
default opEquals and toHash worked just fine.

I honestly have no sympathy for anyone who defined opCmp to be
different from the default opEquals but didn't define opEquals.
Getting that right is simple, and it's trivial to test for you're
unit testing like you should be. I don't want to pay in my code
just to make the compiler friendlier to someone who didn't even
bother to do something so simple. And any code in that situation
has always been broken anyway. I'm _definitely_ not interested in
reducing the performance of existing code in order to fix bugs in
the code of folks who couldn't get opEquals or opCmp right.

I'd much rather be able to take advantage of the fast, default
opEquals and correct toHash than be forced to define them just
because I defined opCmp and didn't want a performance hit on
opEquals.

- Jonathan M Davis
Jacob Carlborg via Digitalmars-d
2014-07-25 11:18:50 UTC
Permalink
Code that worked perfectly fine before is now slower, because it's using
opCmp for opEquals when it wasn't before.
Who says opCmp need to be slower than opEquals.
Even worse, if you define
opEquals, you're then forced to define toHash, which is much harder to
get right.
That might be a problem. But you can always call the one in TypeInfo.
So, in order to avoid a performance hit on opEquals from
defining opCmp
Assuming there is a performance hit.
--
/Jacob Carlborg
H. S. Teoh via Digitalmars-d
2014-07-25 13:33:09 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
1. We're not breaking code where it wasn't broken previously
2. We're fixing broken code. That is when opEqual and opCmp == 0 gave
different results
Code that worked perfectly fine before is now slower, because it's
using opCmp for opEquals when it wasn't before.
I don't understand why you keep bringing up the point of being slower. I
thought the whole point of D was to be safe first, then performant if
you ask for it. In this case, sure there will be a (small!) performance
hit, but then the solution is just to define opEquals yourself -- which
you should have been doing in the first place! So this is really just
prodding the programmer in the right direction.
Even worse, if you define opEquals, you're then forced to define
toHash, which is much harder to get right.
If you're redefining opCmp and opEquals, I seriously question whether
the default toHash actually produces the correct result. If it did, it
begs the question, what's the point of redefining opCmp?
So, in order to avoid a performance hit on opEquals from defining
opCmp, you now have to define toHash, which significantly increases
the chances of bugs. And regardless of the increased risk of bugs,
it's extra code that you shouldn't need to write anyway, because the
normal, default opEquals and toHash worked just fine.
I honestly have no sympathy for anyone who defined opCmp to be
different from the default opEquals but didn't define opEquals.
Getting that right is simple, and it's trivial to test for you're unit
testing like you should be.
Frankly, I find this rather incongruous. First you say that requiring
programmers to define toHash themselves is too high an expectation, then
you say that you have no sympathy on these same programmers 'cos they
can't get their opEquals code right. If it's too much to expect them to
write toHash properly, why would we expect them to write opEquals
correctly either? But if they *are* expected to get opEquals right, then
why is it a problem for them to also get toHash right? I'm honestly
baffled at what your point is.
I don't want to pay in my code just to make the compiler friendlier to
someone who didn't even bother to do something so simple.
[...]

And you don't have to. You just define opEquals correctly as you have
always done, and you pay *nothing*. The only time you pay is when you
forgot to define opEquals -- in which case, which is worse, bad
performance, or incorrect code? Perhaps you have different priorities,
but I'd rather have bad performance than incorrect code, especially
*subtly* wrong code that's very difficult to track down.


[...]
I'd much rather be able to take advantage of the fast, default
opEquals and correct toHash than be forced to define them just because
I defined opCmp and didn't want a performance hit on opEquals.
[...]

So perhaps we should implement `bool opEquals = default;`.


T
--
My program has no bugs! Only unintentional features...
Daniel Murphy via Digitalmars-d
2014-07-25 13:42:59 UTC
Permalink
"H. S. Teoh via Digitalmars-d" wrote in message
Post by H. S. Teoh via Digitalmars-d
So perhaps we should implement `bool opEquals = default;`.
No new syntax.
H. S. Teoh via Digitalmars-d
2014-07-25 13:46:07 UTC
Permalink
Post by Daniel Murphy via Digitalmars-d
"H. S. Teoh via Digitalmars-d" wrote in message
Post by H. S. Teoh via Digitalmars-d
So perhaps we should implement `bool opEquals = default;`.
No new syntax.
*shrug* That's just what Jonathan suggested earlier.

At worst, you could just write:

bool opEquals(T t) {
return typeid(this).equals(&this, &t);
}

It's a little more typing, but surely it's not *that* hard??


T
--
"Hi." "'Lo."
Jonathan M Davis via Digitalmars-d
2014-07-25 18:45:28 UTC
Permalink
On Friday, 25 July 2014 at 13:34:55 UTC, H. S. Teoh via
On Fri, Jul 25, 2014 at 09:46:55AM +0000, Jonathan M Davis via
Even worse, if you define opEquals, you're then forced to
define
toHash, which is much harder to get right.
If you're redefining opCmp and opEquals, I seriously question
whether
the default toHash actually produces the correct result. If it
did, it
begs the question, what's the point of redefining opCmp?
Except that with the current git master, you're forced to define
opEquals just because you define opCmp, which would then force
you to define opCmp. And with your suggested fix of making
opEquals equivalent to lhs.opCmp(rhs) == 0, then _every_ type
with opCmp will have to define toHash, because the default toHash
is for the default opEquals, not for a user-defined opCmp.

And remember that a lot of types have opCmp just to work with
AAs, so all of a sudden, _every_ user-defined type which is used
as an AA key will have to define toHash.
Frankly, I find this rather incongruous. First you say that
requiring
programmers to define toHash themselves is too high an
expectation, then
you say that you have no sympathy on these same programmers
'cos they
can't get their opEquals code right. If it's too much to expect
them to
write toHash properly, why would we expect them to write
opEquals
correctly either? But if they *are* expected to get opEquals
right, then
why is it a problem for them to also get toHash right? I'm
honestly
baffled at what your point is.
opEquals is trivial. toHash is much harder to get right,
especially if you want a hash function that's even halfway decent.

- Jonathan M Davis
Daniel Gibson via Digitalmars-d
2014-07-25 18:54:07 UTC
Permalink
On Friday, 25 July 2014 at 13:34:55 UTC, H. S. Teoh via Digitalmars-d
On Fri, Jul 25, 2014 at 09:46:55AM +0000, Jonathan M Davis via
Even worse, if you define opEquals, you're then forced to define
toHash, which is much harder to get right.
If you're redefining opCmp and opEquals, I seriously question whether
the default toHash actually produces the correct result. If it did, it
begs the question, what's the point of redefining opCmp?
Except that with the current git master, you're forced to define
opEquals just because you define opCmp, which would then force you to
define opCmp.
That sentence doesn't make much sense, did you mean "opHash just because
you define opEquals" or something similar?
opEquals is trivial. toHash is much harder to get right, especially if
you want a hash function that's even halfway decent.
As written before somewhere else, a toHash that is as decent as the
current default, but limited to the fields you actually want, should be
really easy, if phobos exposed a function like
hash_t createHash(T...)(T args)
that does to args what D currently does to all members to create the
default hash.

(I kinda tend towards "whatever, maybe not defaulting opEquals to
a.opCmp(b) == 0 is acceptable, people should stumble upon this when
first reading the documentation on how to overload operators" now, though)

Cheers,
Daniel
Jonathan M Davis via Digitalmars-d
2014-07-25 18:58:41 UTC
Permalink
Post by Daniel Gibson via Digitalmars-d
Post by Jonathan M Davis via Digitalmars-d
On Friday, 25 July 2014 at 13:34:55 UTC, H. S. Teoh via
Digitalmars-d
On Fri, Jul 25, 2014 at 09:46:55AM +0000, Jonathan M Davis via
Even worse, if you define opEquals, you're then forced to
define
toHash, which is much harder to get right.
If you're redefining opCmp and opEquals, I seriously question whether
the default toHash actually produces the correct result. If
it did, it
begs the question, what's the point of redefining opCmp?
Except that with the current git master, you're forced to
define
opEquals just because you define opCmp, which would then force you to
define opCmp.
That sentence doesn't make much sense, did you mean "opHash
just because you define opEquals" or something similar?
You're right. I meant that you would then be forced to define
toHash.

- Jonathan M Davis
via Digitalmars-d
2014-07-25 19:03:15 UTC
Permalink
Post by Jonathan M Davis via Digitalmars-d
On Friday, 25 July 2014 at 13:34:55 UTC, H. S. Teoh via
On Fri, Jul 25, 2014 at 09:46:55AM +0000, Jonathan M Davis via
Even worse, if you define opEquals, you're then forced to
define
toHash, which is much harder to get right.
If you're redefining opCmp and opEquals, I seriously question
whether
the default toHash actually produces the correct result. If it
did, it
begs the question, what's the point of redefining opCmp?
Except that with the current git master, you're forced to
define opEquals just because you define opCmp, which would then
force you to define opCmp. And with your suggested fix of
(assuming you mean "toHash")
Post by Jonathan M Davis via Digitalmars-d
making opEquals equivalent to lhs.opCmp(rhs) == 0, then _every_
type with opCmp will have to define toHash, because the default
toHash is for the default opEquals, not for a user-defined
opCmp.
No, only those types that define opCmp _and_ are going to be used
as AA keys, and that's sensible. All others don't need toHash.
Post by Jonathan M Davis via Digitalmars-d
And remember that a lot of types have opCmp just to work with
AAs, so all of a sudden, _every_ user-defined type which is
used as an AA key will have to define toHash.
No, if a type had only defined opCmp because of the previous AA
(mis)implementation, it needs to be changed with any of the
suggested solutions: If opEquals is not going to be
auto-generated, the user needs to add it, if it is, the user has
the choice between adding toHash, or (more likely, as opCmp
usually isn't necessary) changing opCmp into opEquals.
Walter Bright via Digitalmars-d
2014-07-25 06:50:31 UTC
Permalink
Post by Manu via Digitalmars-d
I don't really see how opCmp == 0 could be unreliable or unintended. It was
deliberately written by the author, so definitely not unintended, and I can't
imagine anybody would ever deliberately ignore the == 0 case when implementing
an opCmp, or produce logic that works for less or greater, but fails for equal.
<= and >= are expressed by opCmp, which imply that testing for equality
definitely works as the user intended.
Yes, that's why it's hard to see that it would break existing code, unless that
existing code had a bug in it that was worked around in some peculiar way.
Post by Manu via Digitalmars-d
In lieu of an opEquals, how can a deliberately implemented opCmp, which we know
works in the == case (otherwise <= or >= wouldn't work either) ever be a worse
choice than an implicitly generated opEquals?
Determining an ordering can sometimes be more expensive. It is, after all,
asking for more information.
Daniel Murphy via Digitalmars-d
2014-07-25 07:21:32 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Determining an ordering can sometimes be more expensive. It is, after all,
asking for more information.
It could also be significantly cheaper, if only a subset of fields need to
be compared.
Jonathan M Davis via Digitalmars-d
2014-07-25 07:31:07 UTC
Permalink
"Walter Bright" wrote in message
Post by Walter Bright via Digitalmars-d
Determining an ordering can sometimes be more expensive. It
is, after all, asking for more information.
It could also be significantly cheaper, if only a subset of
fields need to be compared.
If that's the case, then the default opEquals isn't correct, and
the programmer should have already defined opEquals. If they
didn't, then their code is broken, and I see no reason to
penalize the folks who wrote correct code just to fix someone
else's broken code by then defining opEquals in terms of opCmp.

- Jonathan M Davis
Daniel Murphy via Digitalmars-d
2014-07-25 07:34:50 UTC
Permalink
"Jonathan M Davis" wrote in message
If that's the case, then the default opEquals isn't correct, and the
programmer should have already defined opEquals. If they didn't, then
correct code just to fix someone else's broken code by then defining
opEquals in terms of opCmp.
Just because not all fields _need_ to be compared doesn't mean the default
opEquals was incorrect. The ignored fields could be cached values
calculated from the other fields.
Jonathan M Davis via Digitalmars-d
2014-07-25 07:46:11 UTC
Permalink
Post by Daniel Murphy via Digitalmars-d
"Jonathan M Davis" wrote in message
Post by Jonathan M Davis via Digitalmars-d
If that's the case, then the default opEquals isn't correct,
and the programmer should have already defined opEquals. If
they didn't, then their code is broken, and I see no reason to
penalize the folks who wrote correct code just to fix someone
else's broken code by then defining opEquals in terms of opCmp.
Just because not all fields _need_ to be compared doesn't mean
the default opEquals was incorrect. The ignored fields could
be cached values calculated from the other fields.
True. I didn't think of that. But even if that's the case, if
they don't define opEquals, then they've always been getting an
opEquals which compares all of them. The only place that they
would have gotten better performance would have be when the type
was used as the key in an AA, since that will now use opEquals
instead of opCmp. But if they want to get that efficiency gain,
then they can just define opEquals themselves - and if they
really cared about that gain, they would have already defined
opEquals themselves anyway, because the cases other than AAs
would have been using the default opEquals.

So, while you have a good point that opCmp _can_ be more
efficient than opEquals, it usually isn't, and the places where
that would make a difference should already be defining opEquals
anyway, meaning that changing the default opEquals to use opCmp
wouldn't gain them anything unless they didn't care about that
efficiency gain.

- Jonathan M Davis
Jacob Carlborg via Digitalmars-d
2014-07-25 08:27:01 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Yes, that's why it's hard to see that it would break existing code,
unless that existing code had a bug in it that was worked around in some
peculiar way.
If the type was only used as an AA key and never checked for equivalent
then it worked correctly when opCmp was used for AA keys.

Also, adding an opEqual to call opCmp == 0 will make it work for
equivalent as well, even though it's never used. And it will fix the
breaking change with AA keys.
--
/Jacob Carlborg
Walter Bright via Digitalmars-d
2014-07-25 08:54:02 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Yes, that's why it's hard to see that it would break existing code,
unless that existing code had a bug in it that was worked around in some
peculiar way.
If the type was only used as an AA key and never checked for equivalent then it
worked correctly when opCmp was used for AA keys.
Also, adding an opEqual to call opCmp == 0 will make it work for equivalent as
well, even though it's never used. And it will fix the breaking change with AA
keys.
The thing is, either this suffers from == behaving differently than AAs, or
you've made opEquals superfluous by defining it to be opCmp==0. The latter is a
mistake, as Andrei has pointed out, as opCmp may not have a concept of equality,
and opEquals may not have a concept of ordering.

I.e. it's not just about AAs.
Manu via Digitalmars-d
2014-07-25 11:39:55 UTC
Permalink
On 25 July 2014 16:50, Walter Bright via Digitalmars-d <
Post by Walter Bright via Digitalmars-d
Post by Manu via Digitalmars-d
I don't really see how opCmp == 0 could be unreliable or unintended. It was
deliberately written by the author, so definitely not unintended, and I can't
imagine anybody would ever deliberately ignore the == 0 case when implementing
an opCmp, or produce logic that works for less or greater, but fails for equal.
<= and >= are expressed by opCmp, which imply that testing for equality
definitely works as the user intended.
Yes, that's why it's hard to see that it would break existing code, unless
that existing code had a bug in it that was worked around in some peculiar
way.
Indeed.

In lieu of an opEquals, how can a deliberately implemented opCmp, which we
Post by Walter Bright via Digitalmars-d
Post by Manu via Digitalmars-d
know
works in the == case (otherwise <= or >= wouldn't work either) ever be a worse
choice than an implicitly generated opEquals?
Determining an ordering can sometimes be more expensive. It is, after all,
asking for more information.
Correctness has always been the first criteria to satisfy in D. The user is
always able to produce faster code with deliberate effort, and that's true
in this case too, but you can't have something with a high probability of
being incorrect be the default...?

In lieu of opEquals, and opCmp exists, the probability of being correct is
super-biased towards the user supplied opCmp==0, which must already support
<=/>= and therefore almost certainly correct, than some compiler generated
guess, which has no insight into the object, and can only possibly be
correct in the event you're lucky...

I'm a user who's concerned with performance more than most, but there's no
way I can buy into that argument in this case. It's just wrong, and the
sort of bug that this is likely to produce are highly surprising, very
easily overlooked, and likely result in many lost hours to track down. It's
the sort of bug that nobody wants to be tracking down.

All that said, I'm not even convinced that there would be a performance
advantage anyway. I'd be surprised if the optimiser wouldn't produce
correct code for 'a-b==0' vs 'a==b'. These are trivial things that
optimisers have been extremely good at for decades.
If I had to guess at which one offered a performance advantage, I'd say
that they'd likely be the same (because optimisers work well with that sort
of input), or the advantage would go to the user opCmp.
The reason I say that, is that user supplied opCmp may compare *at most*
every field (and therefore likely perform the same), but in reality,
there's a good chance that the comparison requires comparing only a subset
of fields - a user struct is likely to contain some irrelevant fields,
cache data perhaps, whatever - and therefore comparing less stuff would
more likely yield a performance advantage.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20140725/9fa10b65/attachment-0001.html>
Jacob Carlborg via Digitalmars-d
2014-07-25 08:02:18 UTC
Permalink
Post by Walter Bright via Digitalmars-d
http://www.reddit.com/r/programming/comments/2bl51j/programming_in_d_a_great_online_book_for_learning/cj75gm9
The current scheme breaks existing code - code that was formerly correct
and working.
AAs don't make sense if the notion of == on members is invalid. AAs
formerly required opCmp, and yes, opCmp could be constructed to give
different results for opCmp==0 than ==, but I would expect such an
object to not be used in an AA, again because it doesn't make sense.
Using the default generated opEquals for AAs may break code, such as the
an AA of the structs in the parent post, but it seems unlikely that that
code was correct anyway in an AA (as it would give erratic results).
The code [1] from the original issue, 13179, does have an opCmp which
handles equivalent.
Post by Walter Bright via Digitalmars-d
Kenji's rebuttal https://issues.dlang.org/show_bug.cgi?id=13179#c2 is
probably the best counter-argument, and I'd go with it if it didn't
result in code breakage.
I still don't see the problem:

1. If neither opCmp or opEquals are defined, the compiler will
automatically generate these and will be used for comparison and equivalent

2. If opEquals is defined, lhs == rhs will be lowered to lhs.opEquals(rhs)

3. If opCmp is defined but no opEquals, lhs == rhs will be lowered to
lhs.opCmp(rhs) == 0

4. If opCmp and opEquals is defined, lhs == rhs will be lowered to
lhs.opEquals(rhs)

The only case this will break code is when opCmp was defined and
opEquals was not defined where lhs.opCmp(rhs) == 0 and lhs == rhs gave
different results. Many here think this is a bug in the first place. If
anything, this change would fix broken code.

[1]
https://github.com/SiegeLord/Tango-D2/blob/d2port/tango/text/Regex.d#L2345
--
/Jacob Carlborg
Walter Bright via Digitalmars-d
2014-07-25 08:39:11 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
3. If opCmp is defined but no opEquals, lhs == rhs will be lowered to
lhs.opCmp(rhs) == 0
This is the sticking point. opCmp and opEquals are separate on purpose, see
Andrei's posts.
Regan Heath via Digitalmars-d
2014-07-25 11:10:31 UTC
Permalink
On Fri, 25 Jul 2014 09:39:11 +0100, Walter Bright
Post by Walter Bright via Digitalmars-d
Post by Jacob Carlborg via Digitalmars-d
3. If opCmp is defined but no opEquals, lhs == rhs will be lowered to
lhs.opCmp(rhs) == 0
This is the sticking point. opCmp and opEquals are separate on purpose,
see Andrei's posts.
Sure, Andrei makes a valid point .. for a minority of cases. The majority
case will be that opEquals and opCmp==0 will agree. In those minority
cases where they are intended to disagree the user will have intentionally
defined both, to be different. I cannot think of any case where a user
will intend for these to be different, then not define both to ensure it.

R
--
Using Opera's revolutionary email client: http://www.opera.com/mail/
H. S. Teoh via Digitalmars-d
2014-07-25 13:43:15 UTC
Permalink
Post by Regan Heath via Digitalmars-d
On Fri, 25 Jul 2014 09:39:11 +0100, Walter Bright
Post by Walter Bright via Digitalmars-d
Post by Jacob Carlborg via Digitalmars-d
3. If opCmp is defined but no opEquals, lhs == rhs will be lowered
to lhs.opCmp(rhs) == 0
This is the sticking point. opCmp and opEquals are separate on
purpose, see Andrei's posts.
Sure, Andrei makes a valid point .. for a minority of cases. The
majority case will be that opEquals and opCmp==0 will agree. In those
minority cases where they are intended to disagree the user will have
intentionally defined both, to be different. I cannot think of any
case where a user will intend for these to be different, then not
define both to ensure it.
[...]

Exactly!! I don't understand why people keep bringing up non-linear
partial orderings -- those only apply in a *minority* of cases! (Raise
your hands if your code depends on non-linear partial orderings. How
many of you *require* this more often than linear orderings? Yeah, I
thought so.) Why are we sacrificing *common* case -- where opCmp defines
a linear ordering -- for the minority case?

And it's not like we're making it impossible in the minority case -- if
you want a non-linear partial ordering, wouldn't you make sure to define
both opCmp and opEquals so that they do the right thing? Since it's an
uncommon use case, people would tend to be more careful when
implementing it. I argue that it's in the *common* case of linear
orderings, where people are more liable to assume (incorrectly, it
seems!) that opEquals should default to opCmp()==0 -- and that's the
case we should be addressing. Let's not lose sight of the forest for the
minority of the trees.


T
--
The peace of mind---from knowing that viruses which exploit Microsoft system vulnerabilities cannot touch Linux---is priceless. -- Frustrated system administrator.
Tobias Pankrath via Digitalmars-d
2014-07-25 14:46:08 UTC
Permalink
On Friday, 25 July 2014 at 13:44:54 UTC, H. S. Teoh via
On Fri, Jul 25, 2014 at 12:10:31PM +0100, Regan Heath via
Post by Regan Heath via Digitalmars-d
On Fri, 25 Jul 2014 09:39:11 +0100, Walter Bright
Post by Walter Bright via Digitalmars-d
Post by Jacob Carlborg via Digitalmars-d
3. If opCmp is defined but no opEquals, lhs == rhs will be
lowered
to lhs.opCmp(rhs) == 0
This is the sticking point. opCmp and opEquals are separate on
purpose, see Andrei's posts.
Sure, Andrei makes a valid point .. for a minority of cases.
The
majority case will be that opEquals and opCmp==0 will agree.
In those
minority cases where they are intended to disagree the user
will have
intentionally defined both, to be different. I cannot think
of any
case where a user will intend for these to be different, then
not
define both to ensure it.
[...]
Exactly!! I don't understand why people keep bringing up
non-linear
partial orderings -- those only apply in a *minority* of cases!
(Raise
your hands if your code depends on non-linear partial
orderings. How
many of you *require* this more often than linear orderings?
Yeah, I
thought so.) Why are we sacrificing *common* case -- where
opCmp defines
a linear ordering -- for the minority case?
And it's not like we're making it impossible in the minority
case -- if
you want a non-linear partial ordering, wouldn't you make sure
to define
both opCmp and opEquals so that they do the right thing? Since
it's an
uncommon use case, people would tend to be more careful when
implementing it.
Do I miss something or wouldn't an non-linear ordering imply,
that x.opCmp(y) != 0 for all x,y ∈ T and thus automatically
generating opEquals to opCmd() == 0 would automatically do the
right thing in this case?

So the amount of people that require a different opEquals are
even smaller
and defining opEquals and opCmp for two different orderings is a
code smell squared.
Tobias Pankrath via Digitalmars-d
2014-07-25 14:52:11 UTC
Permalink
Post by Tobias Pankrath via Digitalmars-d
Post by H. S. Teoh via Digitalmars-d
And it's not like we're making it impossible in the minority
case -- if
you want a non-linear partial ordering, wouldn't you make sure
to define
both opCmp and opEquals so that they do the right thing? Since
it's an
uncommon use case, people would tend to be more careful when
implementing it.
Do I miss something or wouldn't an non-linear ordering imply,
that x.opCmp(y) != 0 for all x,y ∈ T and thus automatically
generating opEquals to opCmd() == 0 would automatically do the
right thing in this case?
So the amount of people that require a different opEquals are
even smaller
and defining opEquals and opCmp for two different orderings is
a code smell squared.
A nevermind, got my hands on a coffee now.
via Digitalmars-d
2014-07-25 19:21:04 UTC
Permalink
On Friday, 25 July 2014 at 13:44:54 UTC, H. S. Teoh via
Post by H. S. Teoh via Digitalmars-d
Exactly!! I don't understand why people keep bringing up
non-linear
partial orderings -- those only apply in a *minority* of cases!
Well, if <, <= etc are only to be used where you have a "natural"
total order then I guess you are right, but then opCmp should be
limited to those types.

Since comparison and boolean operators (&& etc) cannot be defined
in a general manner that will work with all branches of
mathematics, maybe they should be limited to total orders.

It is awfully limiting for DSL-style programming, though. As I've
pointed out before, how D limits && and || prevents readable
fuzzy logic and opCmp prevents useful fuzzy number operators. So,
since the limits are there already, maybe forbidding orders on
complex types, and vectors etc is sensible too
 (Why limit some
types and not others?)
Post by H. S. Teoh via Digitalmars-d
Since it's an
uncommon use case, people would tend to be more careful when
implementing it. I argue that it's in the *common* case of
linear orderings, where people are more liable to assume
It is quite common to want an order on domain-specific objects
without discriminating all cases, unless you do direct comparison
for equality. Say if you have a colour type you might want an
order on chromacity, but not on intensity. If you have a vector,
you might want an order on magnitude, but not on direction. If
you have a normal vector you might want an order on acute angles,
e.g. define a<b === is_acute_angle(a,b).

If opCmp automatically defines equality, then you have to
remember to undefine it. Equality as opCmp would be slow and
wrong in the case of "order by magnitude":

(a.x*a.x + a.y*a.y) == (b.x*b.x + b.y*b.y)

This can go undetected by the programmer if you use a mixin to
add "standard operators" or if you don't care about equality in
the actual type, but it is used for equality comparison in an
aggregate (a struct that has the type as a field).

A more sensible approach from a correctness viewpoint is to
require equality to be defined explicitly if you provide opCmp. I
mean, if you want to argue for CORRECTNESS, take it all the way.
Not 50% convinience, 50% correctness, and a dash flexibility, but
not quite enough to cover the most pragmatic branches of
mathematics

Jonathan M Davis via Digitalmars-d
2014-07-25 10:07:35 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
1. If neither opCmp or opEquals are defined, the compiler will
automatically generate these and will be used for comparison
and equivalent
2. If opEquals is defined, lhs == rhs will be lowered to
lhs.opEquals(rhs)
3. If opCmp is defined but no opEquals, lhs == rhs will be
lowered to lhs.opCmp(rhs) == 0
4. If opCmp and opEquals is defined, lhs == rhs will be lowered
to lhs.opEquals(rhs)
The compiler _never_ defines opCmp for you. You have to do that
yourself. So, what you're suggesting would force people to define
opEquals just because they defined opCmp unless they wanted to
take a performance hit. And once you define opEquals, you have to
define toHash. So, what you're suggesting would force a lot more
code to define toHash, which will likely cause far more bugs than
simply requiring that the programmer define opEquals if that's
required in order to make it consistent with opEquals.

- Jonathan M Davis
Daniel Murphy via Digitalmars-d
2014-07-25 10:11:12 UTC
Permalink
"Jonathan M Davis" wrote in message
The compiler _never_ defines opCmp for you. You have to do that yourself.
So, what you're suggesting would force people to define opEquals just
because they defined opCmp unless they wanted to take a performance hit
<<<<<<<< in the rare case that it actually matters >>>>>>>>>>.
And once you define opEquals, you have to define toHash. So, what you're
suggesting would force a lot more code to define toHash, which will likely
cause far more bugs than simply requiring that the programmer define
opEquals if that's required in order to make it consistent with opEquals.
Jonathan M Davis via Digitalmars-d
2014-07-25 10:39:33 UTC
Permalink
Post by Daniel Murphy via Digitalmars-d
"Jonathan M Davis" wrote in message
Post by Jonathan M Davis via Digitalmars-d
The compiler _never_ defines opCmp for you. You have to do
that yourself. So, what you're suggesting would force people
to define opEquals just because they defined opCmp unless they
wanted to take a performance hit <<<<<<<< in the rare case
that it actually matters >>>>>>>>>>.
Equality checks are a common operation, so it will affect a fair
bit of code. Granted, how much it will really matter is an open
question, but there will be a small reduction in speed to quite a
bit of code out there.

But regardless of whether the efficiency cost is large, you're
talking about incurring it just to fix the code of folks who
couldn't be bothered to make sure that opEquals and
lhs.opCmp(rhs) == 0 were equivalent. You'd be punishing correct
code (however slight that punishment may be) in order to fix the
code of folks who didn't even properly test basic functionality.
I see no reason to care about trying to help out folks who can't
even be bothered to test opEquals and opCmp, especially when that
help isn't free.

- Jonathan M Davis
Jacob Carlborg via Digitalmars-d
2014-07-25 12:06:05 UTC
Permalink
But regardless of whether the efficiency cost is large, you're talking
about incurring it just to fix the code of folks who couldn't be
bothered to make sure that opEquals and lhs.opCmp(rhs) == 0 were
equivalent. You'd be punishing correct code (however slight that
punishment may be) in order to fix the code of folks who didn't even
properly test basic functionality. I see no reason to care about trying
to help out folks who can't even be bothered to test opEquals and opCmp,
especially when that help isn't free.
By Walter and Andrei's definition opCmp is not to be used for
equivalent, therefor opCmp does never need to be equal to 0.
--
/Jacob Carlborg
Manu via Digitalmars-d
2014-07-25 12:15:44 UTC
Permalink
On 25 July 2014 22:06, Jacob Carlborg via Digitalmars-d <
But regardless of whether the efficiency cost is large, you're talking
about incurring it just to fix the code of folks who couldn't be
bothered to make sure that opEquals and lhs.opCmp(rhs) == 0 were
equivalent. You'd be punishing correct code (however slight that
punishment may be) in order to fix the code of folks who didn't even
properly test basic functionality. I see no reason to care about trying
to help out folks who can't even be bothered to test opEquals and opCmp,
especially when that help isn't free.
By Walter and Andrei's definition opCmp is not to be used for equivalent,
therefor opCmp does never need to be equal to 0.
Yes it does, <= and >= are both things that you can type.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20140725/80ab8e96/attachment.html>
Daniel Gibson via Digitalmars-d
2014-07-25 10:27:27 UTC
Permalink
And once you define opEquals, you have to define
toHash. So, what you're suggesting would force a lot more code to define
toHash, which will likely cause far more bugs than simply requiring that
Is it actually hard to define toHash, or should it be?
What is done by default? I guess some magic hash is built over all
members of a type (like all members are compared in opEquals).
So couldn't there be some templated function that creates the hash for
you in the same way as it's done now, but only for the values you want
to hash?

e.g.
hash_t createHash(T...)(T args) {
return (do magic with args);
}


struct Foo {
int x;
int y;
string str;
int dontCare;

bool opEquals()(auto ref const Foo o) const {
return x == o.x && y == o.y && str == o.str;
}

hash_t toHash() {
return createHash(x, y, str);
}
}

Cheers,
Daniel
Jonathan M Davis via Digitalmars-d
2014-07-25 10:43:27 UTC
Permalink
Post by Daniel Gibson via Digitalmars-d
And once you define opEquals, you have to define
toHash. So, what you're suggesting would force a lot more code to define
toHash, which will likely cause far more bugs than simply
requiring that
Is it actually hard to define toHash, or should it be?
What is done by default? I guess some magic hash is built over
all members of a type (like all members are compared in
opEquals).
So couldn't there be some templated function that creates the
hash for you in the same way as it's done now, but only for the
values you want to hash?
Sure. We could create something like that, and we probably
should. It would help out in cases where the default wasn't
appropriate (e.g. only some of the member variables were part of
opEquals). But why force folks to define opEquals and toHash when
the defaults would have worked fine for them just to fix the code
of folks who didn't make the effort to test that opEquals and
lhs.opCmp(rhs) == 0 were equivalent? That seems to me like we're
punishing the folks who actually write good code and test it in
order to help those who don't even test the basic functionality
of their types.

- Jonathan M Davis
Jacob Carlborg via Digitalmars-d
2014-07-25 11:19:53 UTC
Permalink
Post by Jonathan M Davis via Digitalmars-d
The compiler _never_ defines opCmp for you. You have to do that
yourself. So, what you're suggesting would force people to define
opEquals just because they defined opCmp unless they wanted to take a
performance hit. And once you define opEquals, you have to define
toHash. So, what you're suggesting would force a lot more code to define
toHash, which will likely cause far more bugs than simply requiring that
the programmer define opEquals if that's required in order to make it
consistent with opEquals.
Again, you're assuming there will be a performance hit.
--
/Jacob Carlborg
Walter Bright via Digitalmars-d
2014-07-25 08:48:47 UTC
Permalink
Putting it simply,

1. == uses opEquals. If you don't supply opEquals, the compiler will make one
for you.

2. AAs use ==. See rule 1.


Easy to understand, easy to explain, easy to document.
Jacob Carlborg via Digitalmars-d
2014-07-25 09:12:11 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Putting it simply,
1. == uses opEquals. If you don't supply opEquals, the compiler will
make one for you.
2. AAs use ==. See rule 1.
Easy to understand, easy to explain, easy to document.
It's very hard to use D when it constantly changes and breaks code. It's
especially annoying reading your comments on reddit that we must stop
break code. Then a few days later go an break code. I really hope no one
gets false hopes from those comments.
--
/Jacob Carlborg
Walter Bright via Digitalmars-d
2014-07-25 09:37:46 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
Post by Walter Bright via Digitalmars-d
Putting it simply,
1. == uses opEquals. If you don't supply opEquals, the compiler will
make one for you.
2. AAs use ==. See rule 1.
Easy to understand, easy to explain, easy to document.
It's very hard to use D when it constantly changes and breaks code. It's
especially annoying reading your comments on reddit that we must stop break
code. Then a few days later go an break code. I really hope no one gets false
hopes from those comments.
We went through the likely code breakage from this in this thread, and it's hard
to see any non-broken code breaking. It will also fix regression
https://issues.dlang.org/show_bug.cgi?id=13179 and stop that breakage.
Jacob Carlborg via Digitalmars-d
2014-07-25 12:10:46 UTC
Permalink
Post by Walter Bright via Digitalmars-d
We went through the likely code breakage from this in this thread, and
it's hard to see any non-broken code breaking. It will also fix
regression https://issues.dlang.org/show_bug.cgi?id=13179 and stop that
breakage.
So opEquals will not be required to be defined if opCmp is defined if
it's used as an AA key?
--
/Jacob Carlborg
H. S. Teoh via Digitalmars-d
2014-07-25 14:08:23 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Post by Jacob Carlborg via Digitalmars-d
Post by Walter Bright via Digitalmars-d
Putting it simply,
1. == uses opEquals. If you don't supply opEquals, the compiler will
make one for you.
2. AAs use ==. See rule 1.
Easy to understand, easy to explain, easy to document.
It's very hard to use D when it constantly changes and breaks code.
It's especially annoying reading your comments on reddit that we must
stop break code. Then a few days later go an break code. I really
hope no one gets false hopes from those comments.
We went through the likely code breakage from this in this thread, and
it's hard to see any non-broken code breaking. It will also fix
regression https://issues.dlang.org/show_bug.cgi?id=13179 and stop
that breakage.
You're missing the fact that:

1) Before we fixed the use of typeinfo.compare in aaA.d, users were
*required* to define opCmp in order for their types to be usable as AA
keys -- because that's what aaA.d used to compare keys. This applies
even if the user type has no meaningful partial ordering -- you still
had to define opCmp because otherwise it just plain won't work properly.

2) Because of (1), there's now code out there that defines opCmp for
user types just so that they can be used as AA keys. But we're now
breaking that by saying "nyah nyah we're now using opEquals for AA keys,
so your opCmp don't work no more, sux to be you!".
prior to 2.066, they were told "you must define opCmp otherwise your AA
keys won't work properly". So like obedient little lambs they went ahead
and did that. And now in 2.066 we're saying "you must define opEquals
otherwise your AA keys won't work properly -- and all your opCmp's are
now useless 'cos that was wrong in the first place". From the
perspective of the user, this seems like unreasonable code breakage --
first they *already* expected in the past that their code should've
worked with opEquals, but they were told to use opCmp because AA's were
buggy. Yet now we're going back on our word and saying that opCmp was
wrong -- the very workaround we recommended in the past -- and that now
they need to define opEquals instead, which didn't work before. This
gives users the perception that we're constantly going back on our word
and breaking prior code and annulling previously recommended workarounds
without any warning.

The whole reason the opCmp()==0 thing was brought up, was to eliminate
this frustration -- give users a nice way to transition into the correct
AA design of using opEquals for their keys, instead of just outright
breaking past recommendations in their face with no warning.


T
--
Always remember that you are unique. Just like everybody else. -- despair.com
via Digitalmars-d
2014-07-25 16:11:27 UTC
Permalink
On Friday, 25 July 2014 at 14:10:11 UTC, H. S. Teoh via
Post by H. S. Teoh via Digitalmars-d
The whole reason the opCmp()==0 thing was brought up, was to
eliminate
this frustration -- give users a nice way to transition into
the correct
AA design of using opEquals for their keys, instead of just
outright
breaking past recommendations in their face with no warning.
Not just that, it's also the right thing to do, independently
from AAs. I'm astonished that it doesn't work like that already.
When I first read the operator overloading docs, I really liked
that in D I don't need to define all the individual comparison
operators, but only opCmp. I totally expected this to include
opEquals, too, which I thought was just an option that could be
used to enhance performance, but which could be safely ignored
otherwise. It's really bad if this isn't the case, because then
there is nothing telling you that you need an opEquals, it will
just silently compile and appear to be working.
Daniel Gibson via Digitalmars-d
2014-07-25 18:54:15 UTC
Permalink
I'm astonished that it doesn't work like that already. When I first read
the operator overloading docs, I really liked that in D I don't need to
define all the individual comparison operators, but only opCmp. I
Well, to be fair the documentation, is pretty explicit about it, the
headings are "Overloading == and !=" and "Overloading <, <=, <, and <=".
The D1 documentation even had a rationale why there's both opEquals and
opCmp, no idea why that was dropped for D2.

However, I read about opCmp at some time and in the meantime forgot
about the "not for ==" part - but this is probably a problem with my
brain (or the long timespan) and not with the documentation.

Cheers,
Daniel
via Digitalmars-d
2014-07-25 19:10:50 UTC
Permalink
Post by Daniel Gibson via Digitalmars-d
I'm astonished that it doesn't work like that already. When I
first read
the operator overloading docs, I really liked that in D I
don't need to
define all the individual comparison operators, but only
opCmp. I
Well, to be fair the documentation, is pretty explicit about
it, the headings are "Overloading == and !=" and "Overloading
<, <=, <, and <=".
Whatever the outcome of the discussion will be, it needs to be
documented much better. The current documentation doesn't say
anything about whether or not, and how opEquals and opCmp relate.
I doesn't even mention that they are supposed to be consistent.

I'm just afraid that it will not be noticed, because it will be
"hidden" in the documentation. If the status quo is kept, you
just won't know you've written wrong code, even though the
compiler has all the means to tell you.
Post by Daniel Gibson via Digitalmars-d
The D1 documentation even had a rationale why there's both
opEquals and opCmp, no idea why that was dropped for D2.
However, I read about opCmp at some time and in the meantime
forgot about the "not for ==" part - but this is probably a
problem with my brain (or the long timespan) and not with the
documentation.
Well, you're not the only one :-(
Jonathan M Davis via Digitalmars-d
2014-07-25 10:09:46 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
Post by Walter Bright via Digitalmars-d
Putting it simply,
1. == uses opEquals. If you don't supply opEquals, the
compiler will
make one for you.
2. AAs use ==. See rule 1.
Easy to understand, easy to explain, easy to document.
It's very hard to use D when it constantly changes and breaks
code. It's especially annoying reading your comments on reddit
that we must stop break code. Then a few days later go an break
code. I really hope no one gets false hopes from those comments.
The _only_ code that would break would be code that's _already_
broken - code that defines opCmp in a way that's inconsistent
with the default opEquals and then doesn't define opEquals. I see
no reason to worry about making sure that we don't break code
that's already broken.

- Jonathan M Davis
Jacob Carlborg via Digitalmars-d
2014-07-25 12:08:55 UTC
Permalink
The _only_ code that would break would be code that's _already_ broken -
code that defines opCmp in a way that's inconsistent with the default
opEquals and then doesn't define opEquals. I see no reason to worry
about making sure that we don't break code that's already broken.
I see no reason why I should define opEquals when opCmp was used for AA
keys. You keep ignoring that argument.
--
/Jacob Carlborg
H. S. Teoh via Digitalmars-d
2014-07-25 13:51:41 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
Post by Jonathan M Davis via Digitalmars-d
The _only_ code that would break would be code that's _already_
broken - code that defines opCmp in a way that's inconsistent with
the default opEquals and then doesn't define opEquals. I see no
reason to worry about making sure that we don't break code that's
already broken.
I see no reason why I should define opEquals when opCmp was used for
AA keys. You keep ignoring that argument.
[...]

AA's don't care about keys being orderable, all they care about is that
keys should have a hash value, and be comparable. It was a mistake to
use opCmp for AA keys in the first place. We're now fixing this mistake.

The issue at hand is really more of easing the transition from the old
buggy design so that we don't break old code where they used to work
correctly.


T
--
Error: Keyboard not attached. Press F1 to continue. -- Yoon Ha Lee, CONLANG
Jonathan M Davis via Digitalmars-d
2014-07-25 18:56:40 UTC
Permalink
Post by Jacob Carlborg via Digitalmars-d
Post by Jonathan M Davis via Digitalmars-d
The _only_ code that would break would be code that's
_already_ broken -
code that defines opCmp in a way that's inconsistent with the
default
opEquals and then doesn't define opEquals. I see no reason to
worry
about making sure that we don't break code that's already
broken.
I see no reason why I should define opEquals when opCmp was
used for AA keys. You keep ignoring that argument.
opEquals will now be used for AA keys, not opCmp. That's why git
master generates errors when you have a struct which defines
opCmp and not opEquals, and you try and use it as an AA key. It
was done on the theory that your opEquals and opCmp might not
match (which would be buggy code to begin with, so it would be
forcing everyone to change their code just because someone might
have gotten their opEquals and opCmp wrong).

If we keep the same behavior as 2.065 but still change the AAs to
use opEquals, then there's no need to define opEquals unless the
type was buggy and defined opCmp in a way that was inconsistent
with the default opEquals and then didn't define one which was
consistent. The code will continue to work.

H.S. Teoh wants to change the default-generated opEquals to be
equivalent to lhs.opCmp(rhs) == 0 in the case where opCmp is
defined in order to avoid further breaking the code of folks
whose code is broken and didn't define opEquals when opCmp didn't
match the default.

So, if we remove the new check for a user-defined opEquals when
opCmp is defined, then you don't have to define opEquals. If we
do what H.S. Teoh suggests, then you'll have to define it if you
want to avoid the additional checks that opCmp would be doing
that opEquals wouldn't do, but if you didn't care, then you
wouldn't. If we leave it as it is in git master, then you'd
always have to define it if you defined opCmp and wanted to use
it as an AA key, and since opCmp was used for AA keys before,
that means that _every_ type which didn't define opEquals but was
used as an AA key will suddenly have to define opEquals and
toHash and will thus now be broken. So, the current situation in
git master is the worst all around.

- Jonathan M Davis
Jacob Carlborg via Digitalmars-d
2014-07-25 20:03:44 UTC
Permalink
Post by H. S. Teoh via Digitalmars-d
AA's don't care about keys being orderable, all they care about is that
keys should have a hash value, and be comparable. It was a mistake to
use opCmp for AA keys in the first place. We're now fixing this mistake.
I'm responding to Jonathan's claims that types that defined opCmp but
not opEquals are broken.
--
/Jacob Carlborg
Loading...