July 28, 2014
On Monday, 28 July 2014 at 06:05:03 UTC, Fool wrote:
> So D has to separate opEquals and opCmp since otherwise a user could not define floating-point 'equality' and 'comparison' himself in the same way as it is defined by the language.
>
> I'm convinced know. :-)

But opCmp does not affect <> and !<>, which is the closest you get to equivalence?

Then again NaN is really bottom, not a proper value, but an exceptional state...


July 28, 2014
On Monday, 28 July 2014 at 00:23:36 UTC, H. S. Teoh via Digitalmars-d wrote:
> On Sun, Jul 27, 2014 at 07:04:08PM +0000, Fool via Digitalmars-d wrote:
>> Similarly, the user is free to define opCmp without restriction. In
>> practice, however, it does not seem to make any sense if <= does not
>> even model a preorder (reflexive and transitive) or one of >, <=, <
>> does not match.
>
> The problem with imposing these kinds of restrictions, is that they are
> generally not enforceable (at least, not without significantly crippling
> legitimate use cases). At some point, we have to stop babysitting the
> programmer and trust that he's competent enough to not try to subvert
> the language to make it do stuff it wasn't intended to do.

That's missing the point completely. If the compiler cannot obtain meta information about the properties of relations then you cannot introduce high level optimization and generic programming becomes crippled and a second rate citizen.
July 28, 2014
On Fri, 25 Jul 2014 21:38:33 +0100, Walter Bright <newshound2@digitalmars.com> wrote:

> On 7/25/2014 4:10 AM, Regan Heath wrote:
>> 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.
>
> You've agreed with my point, then, that autogenerating opEquals as memberwise equality (not opCmp==0) if one is not supplied will be correct unless the user code is already broken.

No, you've miss-understood my point.

My point was that for the vast majority of coders, in the vast majority of cases opCmp()==0 will agree with opEquals().  It is only in very niche cases i.e. where partial ordering is actually present and important, that this assumption should be broken.

Yet, by default, if a user defines opCmp() the compiler generated opEquals may well violate that assumption.  This is surprising and will lead to subtle bugs.

If someone is intentionally defining an object for partial ordering they will expect to have to define both opCmp and opEquals, and not only that, if they somehow neglect to do so their first test of partial ordering will show they have a bug and they will soon realise their mistake.

The same cannot be said for someone who wants total ordering (the majority of users in the majority of cases).  In this case they are unlikely to specifically test for ordering bugs, and this mistake will creep in cause trouble down the line.

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/
July 28, 2014
On Sat, 26 Jul 2014 05:22:26 +0100, Walter Bright <newshound2@digitalmars.com> wrote:
> If you don't want to accept that equality and comparison are fundamentally different operations, I can only repeat saying the same things.

For the majority of use cases they are *not* in fact fundamentally different.

You're correct, they are *actually* fundamentally different at a conceptual/theoretical level, but this difference is irrelevant in the majority of cases.

It is true that we need to be able to define/model this difference (which is why we have both opCmp and opEquals) but it is *not* true that every user, for every object, needs to be aware of and cope with this difference.

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/
July 28, 2014
On Monday, 28 July 2014 at 06:05:03 UTC, Fool wrote:
> On Monday, 28 July 2014 at 00:23:36 UTC, H. S. Teoh via Digitalmars-d wrote:
>> On Sun, Jul 27, 2014 at 07:04:08PM +0000, Fool via
>>> Defining opEquals only makes sense if a user wants to replace equality
>>> by some equivalence relation (different from equality).
>>
>> Not necessarily. The user type may be implemented in a way where
>> member-wise binary comparison is not the correct implementation of
>> equality. For example, it could be a tree structure implemented by
>> integer indices into a backing array of nodes. There is no way the
>> compiler could know, in general, how to correctly compare two instances
>> of such a structure, since the bit-level representation of two equal
>> objects may be completely different, yet they represent equivalent
>> trees. You're still implementing equality, but it's equality that's not
>> the same as binary equality.
>
> I think we agree except for a subtle difference in defining equality and equivalence. In my personal language there is a single equality but there are many equivalences.
>
>
>> The problem with imposing these kinds of restrictions, is that they are
>> generally not enforceable (at least, not without significantly crippling
>> legitimate use cases). At some point, we have to stop babysitting the
>> programmer and trust that he's competent enough to not try to subvert
>> the language to make it do stuff it wasn't intended to do. As somebody
>> once said:
>>
>> 	Unix was not designed to stop people from doing stupid things,
>> 	because that would also stop them from doing clever things.
>> 	-- Doug Gwyn
>>
>> We're not talking about Unix here, but the same principle applies.
>
> I agree.
>
>
>>> Please excuse my lack of creativity: in presence of opCmp I cannot see
>>> a single sensible use case for defining a.opEquals(b) different from
>>> a.opCmp(b) == 0.
>>
>> Floating-point numbers? ;-)
>
> Thank you for pushing me there! It's true.
>
> So D has to separate opEquals and opCmp since otherwise a user could not define floating-point 'equality' and 'comparison' himself in the same way as it is defined by the language.
>
> I'm convinced know. :-)
>
> Thanks!



Be careful, though. The argument that opCmp() and opEquals() are orthogonal is not correct, though. Although they are different concepts, they are closely related.

We must have:  a == b  implies  a.opCmp(b) == 0.

The converse does not apply though.

Otherwise you're abusing operator overloading, like when you define + to mean "reformat hard disk" or something.


Suppose we dealt correctly with floating point, including the <>= operators, etc. Then we'd require another overloaded operator.

bool unordered(X other) // return true if !(this > other) && !(this < other)

Full situation is:

opCmp() == 0   implies  ( a==b || a.unordered(b) )


This applies to the RGB example, too.


If you define opCmp() for a type, then either:
(1) opEquals() is the same as opCmp()==0, OR
(2) opEquals() is weird, and needs to be explicitly defined. What you're really doing is distinguishing the unordered case from the equal case.


IMHO, the ideal solution would be a really smart compiler that can detect violations of (1). At least, it would be fairly simple to add a runtime assert that  this.opCmp(this) == 0 for all cases where opEquals is synthesised.

July 28, 2014
On Monday, 28 July 2014 at 09:37:27 UTC, Regan Heath wrote:
> My point was that for the vast majority of coders, in the vast majority of cases opCmp()==0 will agree with opEquals().  It is only in very niche cases i.e. where partial ordering is actually present and important, that this assumption should be broken.
>
> Yet, by default, if a user defines opCmp() the compiler generated opEquals may well violate that assumption.  This is surprising and will lead to subtle bugs.

The cheap non-breaking solution is to just add opCmpTotal() and map opCmp() to that. If opCmpTotal is defined then you cannot define opCmp() and opCmp(a,b)==0 should match a==b whether redefined or not.
July 28, 2014
On 26 July 2014 00:31, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On 7/25/2014 4:28 PM, H. S. Teoh via Digitalmars-d wrote:
>>
>> That page doesn't say anything about TypeInfo, though.
>
>
> It says to follow the form.
>
>
>> But even then,
>> why doesn't the compiler reject opCmp signatures that don't match the
>> compiler's expectations?
>
>
> Because you may want to use an opCmp for other purposes.
>

I think this is bad practice.  I did write a terse API for a library I'm building up, where:

return a < b;

Would return codegen describing the operation of (a < b).

I had second thoughts about it though, mostly because it was perhaps a bit *too* magical. :)

Iain
July 28, 2014
On 07/23/2014 06:45 PM, H. S. Teoh via Digitalmars-d wrote:
> 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
>          }
> }
> ...

Not even transitive!

void main() {
    auto a=S(~0<<31);
    auto b=S(0);
    auto c=S(1);
    assert(a<b); // OK
    assert(b<c); // OK
    assert(a<c); // FAIL
}

=P


> ...
>
> 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
>
> :-(
> ...

My 5 cents:

There seems to be confusion about whether a.opCmp(b)==0 means that a and b are equal, or that a and b are unordered.

1. Based on the current rewrite rules, making operators <= and >= yield true if opCmp returns 0, a.opCmp(b)==0 means that a and b are equal, and one should follow the following general rules:

a < b ⇔ b > a
a.opCmp(b)==0 ⇒ a == b
a.opCmp(b)<>=0 && a == b ⇒ a.opCmp(b)==0

In particular, if opCmp returns a totally ordered type:

a.opCmp(b) ⇔ a == b

So, rewriting a == b to a.opCmp(b) would make sense in terms of semantics, given that opCmp returns such an ordered type.

2. If a.opCmp(b)==0 actually means that a and b are unordered, then <= and >= are currently rewritten the wrong way.

This is fixed, by eg making: a <= b → a<b||a.opEquals(b), avoiding repeated evaluation of side-effects.

To get the current behaviour of <= and >=, one should then use !> and !<.

To me, the current situation seems to be that DMD assumes meaning 1 and Phobos assumes meaning 2.


August 16, 2014
On Wednesday, 23 July 2014 at 21:36:16 UTC, Andrei Alexandrescu wrote:
> There is this notion of partial ordering that makes objects not smaller and not greater than others, yet not equal. -- Andrei

How do you define a partial ordering using opCmp?
August 17, 2014
On Friday, 25 July 2014 at 22:29:30 UTC, Walter Bright wrote:
> On 7/25/2014 2:55 PM, Jonathan M Davis wrote:
>> Doesn't the compiler-generated opEquals do a memcmp when it can?
>
> Yes.

And even sometime when it cannot :D (floats for instance).