Jump to page: 1 29  
Page
Thread overview
ProtoObject and comparison for equality and ordering
May 14, 2019
12345swordy
May 14, 2019
Adam D. Ruppe
May 14, 2019
Eduard Staniloiu
May 14, 2019
Mike Franklin
May 14, 2019
Seb
May 15, 2019
Mike Franklin
May 15, 2019
Mike Franklin
May 15, 2019
Mike Franklin
May 15, 2019
H. S. Teoh
May 16, 2019
Mike Franklin
May 16, 2019
Noob
May 16, 2019
H. S. Teoh
May 15, 2019
Seb
May 15, 2019
Adam D. Ruppe
May 15, 2019
Adam D. Ruppe
May 15, 2019
Seb
May 15, 2019
Mike Franklin
May 15, 2019
Jacob Carlborg
May 15, 2019
Jonathan M Davis
May 15, 2019
H. S. Teoh
May 15, 2019
Timon Gehr
May 15, 2019
H. S. Teoh
May 15, 2019
Jonathan M Davis
May 15, 2019
H. S. Teoh
May 15, 2019
Jonathan M Davis
May 15, 2019
Adam D. Ruppe
May 15, 2019
Jonathan Marler
May 15, 2019
Walter Bright
May 15, 2019
H. S. Teoh
May 14, 2019
Mike Franklin
May 14, 2019
SimonN
May 14, 2019
Jonathan M Davis
May 14, 2019
H. S. Teoh
May 15, 2019
Mike Franklin
May 14, 2019
Adam D. Ruppe
May 15, 2019
Adam D. Ruppe
May 15, 2019
Adam D. Ruppe
May 15, 2019
SimonN
May 15, 2019
Jonathan M Davis
May 15, 2019
H. S. Teoh
May 14, 2019
H. S. Teoh
May 14, 2019
Mike Franklin
May 14, 2019
Mike Franklin
May 14, 2019
H. S. Teoh
May 15, 2019
Jonathan M Davis
May 15, 2019
Adam D. Ruppe
May 15, 2019
Jonathan M Davis
May 14, 2019
Jonathan M Davis
May 15, 2019
Sebastiaan Koppe
May 15, 2019
Patrick Schluter
May 15, 2019
Adam D. Ruppe
May 16, 2019
Jonathan M Davis
May 16, 2019
Jesse Phillips
May 16, 2019
Jonathan M Davis
May 16, 2019
Jonathan M Davis
May 16, 2019
H. S. Teoh
May 17, 2019
Mike Franklin
May 16, 2019
H. S. Teoh
May 16, 2019
Jacob Carlborg
May 14, 2019
In designing ProtoObject and comparison for equality and ordering, we've assumed all class objects are supposed to be comparable (including ProtoObject themselves). That means code like this should always compile:

bool fun(C, D)(C x, D y) if (is(C == class) && is(D == class))
{
   return x < y && x == y;
}

That is, any two class objects should be comparable for equality (==, !=) and ordering (<. >, <=, >=). The decision whether comparison actually works for the types involved is deferred to runtime.

This is in keeping with Java, C#, and existing D where Object has built-in means for comparison.

However the question Jonathan M Davis asked got me thinking - perhaps we should break with tradition and opt for a more statically-checked means of comparison. The drawback is that some objects would NOT be comparable, which may surprise some users.

As a consequence, for example, creating hash tables keyed on certain types will not work. This is not quite unheard of as a type could disable opEquals. Also, by default struct types cannot be compared for ordering - they must define opCmp.

Should we go with a more statically-checked/imposed approach with comparison, or stick with OOP tradition? Ideas welcome.
May 14, 2019
On Tuesday, 14 May 2019 at 19:34:12 UTC, Andrei Alexandrescu wrote:
> In designing ProtoObject and comparison for equality and ordering, we've assumed all class objects are supposed to be comparable (including ProtoObject themselves). That means code like this should always compile:
>
> bool fun(C, D)(C x, D y) if (is(C == class) && is(D == class))
> {
>    return x < y && x == y;
> }
>
> That is, any two class objects should be comparable for equality (==, !=) and ordering (<. >, <=, >=). The decision whether comparison actually works for the types involved is deferred to runtime.
>
> This is in keeping with Java, C#, and existing D where Object has built-in means for comparison.
>
> However the question Jonathan M Davis asked got me thinking - perhaps we should break with tradition and opt for a more statically-checked means of comparison. The drawback is that some objects would NOT be comparable, which may surprise some users.
>
> As a consequence, for example, creating hash tables keyed on certain types will not work. This is not quite unheard of as a type could disable opEquals. Also, by default struct types cannot be compared for ordering - they must define opCmp.
>
> Should we go with a more statically-checked/imposed approach with comparison, or stick with OOP tradition? Ideas welcome.

I say go for statically-checked/imposed approach means of comparison. As matter as fact do it for deconstructor as well as other features. Currently I can't create a deallocate function for class in a @nogc safe context without resorting to workarounds and hacks involving hidden symbols. This is a major pet peeve of mine that I have with D for years, which makes me consider it a hobby rather an serious language.

May 14, 2019
On Tuesday, 14 May 2019 at 19:34:12 UTC, Andrei Alexandrescu wrote:
> In designing ProtoObject

I think ProtoObject should define *absolutely nothing*. If an object wants to be comparable, let it implement opEquals and opCmp itself (and this is D, we can use template mixins too).

We do generically have the `is` keyword for comparing any object for identity. Beyond that, non-identity equality might not even make sense for some objects... and comparison I don't think makes sense for *most* classes.

So no-brainer for me, go with the static approach.
May 14, 2019
On 5/14/19 4:00 PM, Adam D. Ruppe wrote:
> On Tuesday, 14 May 2019 at 19:34:12 UTC, Andrei Alexandrescu wrote:
>> In designing ProtoObject
> 
> I think ProtoObject should define *absolutely nothing*.

That is the case regardless. The question is whether the global __cmp accepts ProtoObjects.
May 14, 2019
On Tue, May 14, 2019 at 08:00:09PM +0000, Adam D. Ruppe via Digitalmars-d wrote:
> On Tuesday, 14 May 2019 at 19:34:12 UTC, Andrei Alexandrescu wrote:
> > In designing ProtoObject
> 
> I think ProtoObject should define *absolutely nothing*. If an object wants to be comparable, let it implement opEquals and opCmp itself (and this is D, we can use template mixins too).
[...]

Yeah, from the Dconf slides, I was under the impression that ProtoObject is supposed to contain absolute nothing at all, with a bunch of optional interfaces like Comparable, Hashable, Stringifiable, Synchronizable, etc., that define the corresponding operations *for those classes that need it*.

But going with the static approach is even better than using interfaces, because then you can have the compiler infer attributes for opEquals, opCmp, etc., without suffering the limitations of the current Object.opXXX and the associated attributes.

So yeah, I also vote for the static approach.


T

-- 
Dogs have owners ... cats have staff. -- Krista Casada
May 14, 2019
On Tuesday, 14 May 2019 at 20:07:08 UTC, Andrei Alexandrescu wrote:
> On 5/14/19 4:00 PM, Adam D. Ruppe wrote:
>> On Tuesday, 14 May 2019 at 19:34:12 UTC, Andrei Alexandrescu wrote:
>>> In designing ProtoObject
>> 
>> I think ProtoObject should define *absolutely nothing*.
>
> That is the case regardless. The question is whether the global __cmp accepts ProtoObjects.

Adding some more context to this

The current design proposal states that we will have an empty ProtoObject as the root of all classes and interfaces that define what behaviours implementing types can achieve.

The proposed design for the Ordered interface is
```
interface Ordered
{
    const @nogc nothrow pure @safe scope
    int opCmp(scope const ProtoObject rhs);
}
```

Any class type that desires to be comparable should implement Ordered.
The reason why `Ordered`'s `opCmp` takes a `ProtoObject` is, as Andrei said, that there might
be some cases where we would have lost all compile time information and we are comparing two `ProtoObjects`. This interface solves this issue, but at the cost of imposing the function attributes on the user, instead of inferring them.

Jonathan's question got us to the point raised: maybe it doesn't make much sense to be able to compare two `ProtoObjects`, so maybe you shouldn't be able to. This would change the interface to
```
interface Ordered(T)
{
    int opCmp(scope const T rhs);
}
```

Now the attributes of `opCmp` will be inferred. The implication of this is that now, if we are in the worst case scenario (comparing two `ProtoObject`s) we can not establish any relationship between the two, the `__cmp` lowering won't be able to compare two.
This implies that the cast (runtime downcast or compile-time cast) will be moved at the call site, in the hands of the user; which might be the right thing to do.

Since we are here, I want to raise another question:
Should `opCmp` return a float?

The reason: when we attempt to compare two types that aren't comparable (an unordered relationship) we can return float.NaN. Thus we can differentiate between a valid -1, 0, 1 and an invalid float.NaN comparison.

Cheers,
Edi
May 14, 2019
On Tuesday, 14 May 2019 at 19:34:12 UTC, Andrei Alexandrescu wrote:
> In designing ProtoObject and comparison for equality and ordering, we've assumed all class objects are supposed to be comparable (including ProtoObject themselves).

What's the rationale for that?  And are you talking about reference equality or value equality?

> That means code like this should always compile:
>
> bool fun(C, D)(C x, D y) if (is(C == class) && is(D == class))
> {
>    return x < y && x == y;
> }
>
> That is, any two class objects should be comparable for equality (==, !=) and ordering (<. >, <=, >=). The decision whether comparison actually works for the types involved is deferred to runtime.

IMO, objects should only support reference equality out of the box.

> This is in keeping with Java, C#, and existing D where Object has built-in means for comparison.

Classes in C# only support reference equality out of the box:  https://dotnetfiddle.net/AOQ0Ry

> However the question Jonathan M Davis asked got me thinking -


> perhaps we should break with tradition and opt for a more statically-checked means of comparison.

Yes, please.

> The drawback is that some objects would NOT be comparable, which may surprise some users.

Not me.

> As a consequence, for example, creating hash tables keyed on certain types will not work. This is not quite unheard of as a type could disable opEquals. Also, by default struct types cannot be compared for ordering - they must define opCmp.
>
> Should we go with a more statically-checked/imposed approach with comparison, or stick with OOP tradition? Ideas welcome.

I don't agree that there is such a tradition, and even if there was, we should be questioning it.

I would like to distinguish between reference equality and value equality.  Reference equality, I think, should be done with the `is` operator, and should probably work out of the box. And I like the idea of users opting into any feature.  If users want to support value equality, they should implement `opEquals`.  If they want comparability they should implement `opCmp`.  If they want to support hashability, the should be required to implement a `getHash` or something along that line of thinking.

> The question is whether the global __cmp accepts ProtoObjects.

No, but it could accept something derived from `ProtObject` that has the necessary requisites, namely `opCmp`, which I believe can be checked statically.

Final thought.  Other languages don't have the design-by-introspection features that D has.  If they did, maybe they wouldn't have chosen the path they did.  D has an opportunity here to lead instead of follow.

Mike

May 14, 2019
On 5/14/19 9:37 PM, Mike Franklin wrote:
> On Tuesday, 14 May 2019 at 19:34:12 UTC, Andrei Alexandrescu wrote:
>> In designing ProtoObject and comparison for equality and ordering, we've assumed all class objects are supposed to be comparable (including ProtoObject themselves).
> 
> What's the rationale for that?

Pretty much principle of least surprise for existing D users.

> And are you talking about reference equality or value equality?

Depends on how the user implements it. The intent is to support meaningful comparison of the content of objects.

> IMO, objects should only support reference equality out of the box.

So that means "x is y" works but not "x == y"?

>> This is in keeping with Java, C#, and existing D where Object has built-in means for comparison.
> 
> Classes in C# only support reference equality out of the box: https://dotnetfiddle.net/AOQ0Ry

Affirmative. Same in Java, is that correct?

> I would like to distinguish between reference equality and value equality.  Reference equality, I think, should be done with the `is` operator, and should probably work out of the box. And I like the idea of users opting into any feature.  If users want to support value equality, they should implement `opEquals`.  If they want comparability they should implement `opCmp`.  If they want to support hashability, the should be required to implement a `getHash` or something along that line of thinking.

Sounds good. If we go with the notion "you can't key a built-in hashtable on any class type" that should work.
May 14, 2019
On Tuesday, 14 May 2019 at 20:36:08 UTC, Eduard Staniloiu wrote:

> Should `opCmp` return a float?
>
> The reason: when we attempt to compare two types that aren't comparable (an unordered relationship) we can return float.NaN. Thus we can differentiate between a valid -1, 0, 1 and an invalid float.NaN comparison.

Seems like a job for an enum, not a float or an integer.

Mike


May 14, 2019
On Tuesday, 14 May 2019 at 21:05:02 UTC, Andrei Alexandrescu wrote:

>> And are you talking about reference equality or value equality?
>
> Depends on how the user implements it. The intent is to support meaningful comparison of the content of objects.

Yeah, C# allows that if the user chooses to overload `Equals`, and I hate it because it allows the author to effectively change the semantics of the `==` operator.  I like that D has a clear distinction between `is` and `==`.  Please keep them distinctly separate.

>> IMO, objects should only support reference equality out of the box.
>
> So that means "x is y" works but not "x == y"?

Yes.

>> Classes in C# only support reference equality out of the box: https://dotnetfiddle.net/AOQ0Ry
>
> Affirmative. Same in Java, is that correct?

Sorry, I don't have any experience with Java in this regard.

Mike


« First   ‹ Prev
1 2 3 4 5 6 7 8 9