Thread overview
opEquals and the Non-Virtual Interface idiom
Sep 27, 2009
grauzone
Sep 27, 2009
Yigal Chripun
Sep 27, 2009
grauzone
Sep 27, 2009
Jeremie Pelletier
Sep 27, 2009
downs
Sep 28, 2009
Justin Johansson
September 27, 2009
Here's an article about the perils of equals in Java (opEquals in D):

http://www.ddj.com/article/printableArticle.jhtml;jsessionid=GFKUCQH5S4IHNQE1GHOSKHWATMY32JVN?articleID=184405053&dept_url=/java/

It turns out this is a great example for NVI. In D, we could and should
do the following:

class Object {
    // Implement this
    private bool opEqualsImpl(Object rhs) {
        return false;
    }
    // Use this
    final bool opEquals(Object rhs) {
        if (this is rhs) return true;
        if (this is null || rhs is null) return false;
        return opEqualsImpl(rhs) && rhs.opEqualsImpl(this);
    }
}

I took advantage of the fact that in a final function this may be null
without an access violation. The implementation above ensures symmetry
of equality and has each class implement a simpler primitive.

What do you think?


Andrei
September 27, 2009
Andrei Alexandrescu wrote:
> Here's an article about the perils of equals in Java (opEquals in D):
> 
> http://www.ddj.com/article/printableArticle.jhtml;jsessionid=GFKUCQH5S4IHNQE1GHOSKHWATMY32JVN?articleID=184405053&dept_url=/java/ 
> 
> 
> It turns out this is a great example for NVI. In D, we could and should
> do the following:
> 
> class Object {
>     // Implement this
>     private bool opEqualsImpl(Object rhs) {
>         return false;
>     }
>     // Use this
>     final bool opEquals(Object rhs) {
>         if (this is rhs) return true;
>         if (this is null || rhs is null) return false;
>         return opEqualsImpl(rhs) && rhs.opEqualsImpl(this);
>     }
> }
> 
> I took advantage of the fact that in a final function this may be null
> without an access violation. The implementation above ensures symmetry
> of equality and has each class implement a simpler primitive.
> 
> What do you think?

Eh, now after all this discussion, we're going to allow even "this" to be null? That seems like a backstep...

Implementing opEquals as a global/static function, that calls the actual Object.opEquals virtual method would be so much more straight forward.

PS: I agree about the NVI thing. If you'd go to extend the language for "NVI", couldn't we just introduce a second type of virtual function that works this way:
1. the super class' implementation is _always_ called first
2. the super class function can decide to "call down" to the sub class' implementation of the same method
=> no extra do<something> method needed, and the code is (possibly) clearer.

> Andrei
September 27, 2009
grauzone wrote:
> Andrei Alexandrescu wrote:
>> Here's an article about the perils of equals in Java (opEquals in D):
>>
>> http://www.ddj.com/article/printableArticle.jhtml;jsessionid=GFKUCQH5S4IHNQE1GHOSKHWATMY32JVN?articleID=184405053&dept_url=/java/ 
>>
>>
>> It turns out this is a great example for NVI. In D, we could and should
>> do the following:
>>
>> class Object {
>>     // Implement this
>>     private bool opEqualsImpl(Object rhs) {
>>         return false;
>>     }
>>     // Use this
>>     final bool opEquals(Object rhs) {
>>         if (this is rhs) return true;
>>         if (this is null || rhs is null) return false;
>>         return opEqualsImpl(rhs) && rhs.opEqualsImpl(this);
>>     }
>> }
>>
>> I took advantage of the fact that in a final function this may be null
>> without an access violation. The implementation above ensures symmetry
>> of equality and has each class implement a simpler primitive.
>>
>> What do you think?
> 
> Eh, now after all this discussion, we're going to allow even "this" to be null? That seems like a backstep...

Good point.

> Implementing opEquals as a global/static function, that calls the actual Object.opEquals virtual method would be so much more straight forward.

It's also less safe because people could call the incomplete primitive by hand. With NVI in place nobody outside object.d could ever call opEqualsImpl.

> PS: I agree about the NVI thing. If you'd go to extend the language for "NVI", couldn't we just introduce a second type of virtual function that works this way:
> 1. the super class' implementation is _always_ called first
> 2. the super class function can decide to "call down" to the sub class' implementation of the same method
> => no extra do<something> method needed, and the code is (possibly) clearer.

Do you know of a precedent for this? One thing that NVI has going for it is that it's quite established - it seems to be solidly planted in programmers' lore. I was surprised to find 2.8M Google matches for the exact string "Non-Virtual Interface".


Andrei

September 27, 2009
grauzone wrote:
> Andrei Alexandrescu wrote:
>> Here's an article about the perils of equals in Java (opEquals in D):
>>
>> http://www.ddj.com/article/printableArticle.jhtml;jsessionid=GFKUCQH5S4IHNQE1GHOSKHWATMY32JVN?articleID=184405053&dept_url=/java/ 
>>
>>
>> It turns out this is a great example for NVI. In D, we could and should
>> do the following:
>>
>> class Object {
>>     // Implement this
>>     private bool opEqualsImpl(Object rhs) {
>>         return false;
>>     }
>>     // Use this
>>     final bool opEquals(Object rhs) {
>>         if (this is rhs) return true;
>>         if (this is null || rhs is null) return false;
>>         return opEqualsImpl(rhs) && rhs.opEqualsImpl(this);
>>     }
>> }
>>
>> I took advantage of the fact that in a final function this may be null
>> without an access violation. The implementation above ensures symmetry
>> of equality and has each class implement a simpler primitive.
>>
>> What do you think?
> 
> Eh, now after all this discussion, we're going to allow even "this" to be null? That seems like a backstep...

How is it a backstep? It's perfectly valid behavior.

Object foo;
foo.opEquals(foo);

The call itself will *always* succeed, its when 'this' gets referenced in opEquals that the code will crash.

> Implementing opEquals as a global/static function, that calls the actual Object.opEquals virtual method would be so much more straight forward.

I agree, I prefer methods to end with Impl to stay hidden instead of being the ones to override.

> PS: I agree about the NVI thing. If you'd go to extend the language for "NVI", couldn't we just introduce a second type of virtual function that works this way:
> 1. the super class' implementation is _always_ called first
> 2. the super class function can decide to "call down" to the sub class' implementation of the same method
> => no extra do<something> method needed, and the code is (possibly) clearer.
> 
>> Andrei

I don't know how useful that would be, when you override a method you usually want to call the super method somewhere in the new implementation, not always at the beginning.
September 27, 2009
On 27/09/2009 19:01, Andrei Alexandrescu wrote:
> grauzone wrote:
>> Andrei Alexandrescu wrote:
>>> Here's an article about the perils of equals in Java (opEquals in D):
>>>
>>> http://www.ddj.com/article/printableArticle.jhtml;jsessionid=GFKUCQH5S4IHNQE1GHOSKHWATMY32JVN?articleID=184405053&dept_url=/java/
>>>
>>>
>>> It turns out this is a great example for NVI. In D, we could and should
>>> do the following:
>>>
>>> class Object {
>>> // Implement this
>>> private bool opEqualsImpl(Object rhs) {
>>> return false;
>>> }
>>> // Use this
>>> final bool opEquals(Object rhs) {
>>> if (this is rhs) return true;
>>> if (this is null || rhs is null) return false;
>>> return opEqualsImpl(rhs) && rhs.opEqualsImpl(this);
>>> }
>>> }
>>>
>>> I took advantage of the fact that in a final function this may be null
>>> without an access violation. The implementation above ensures symmetry
>>> of equality and has each class implement a simpler primitive.
>>>
>>> What do you think?
>>
>> Eh, now after all this discussion, we're going to allow even "this" to
>> be null? That seems like a backstep...
>
> Good point.
>
>> Implementing opEquals as a global/static function, that calls the
>> actual Object.opEquals virtual method would be so much more straight
>> forward.
>
> It's also less safe because people could call the incomplete primitive
> by hand. With NVI in place nobody outside object.d could ever call
> opEqualsImpl.
>
>> PS: I agree about the NVI thing. If you'd go to extend the language
>> for "NVI", couldn't we just introduce a second type of virtual
>> function that works this way:
>> 1. the super class' implementation is _always_ called first
>> 2. the super class function can decide to "call down" to the sub
>> class' implementation of the same method
>> => no extra do<something> method needed, and the code is (possibly)
>> clearer.
>
> Do you know of a precedent for this? One thing that NVI has going for it
> is that it's quite established - it seems to be solidly planted in
> programmers' lore. I was surprised to find 2.8M Google matches for the
> exact string "Non-Virtual Interface".
>
>
> Andrei
>

This is a smalltalk idea -

method:
  ^ self method

by sending a message to self without implementing it you make the method abstract.
of course you can add pre/post code.

I also seen this with the keyword inner:

class Foo {
  void bar() {
      ... setup
      inner();
      ...tear down
  }
}
September 27, 2009
Jeremie Pelletier wrote:
> grauzone wrote:
>> Andrei Alexandrescu wrote:
>>> Here's an article about the perils of equals in Java (opEquals in D):
>>>
>>> http://www.ddj.com/article/printableArticle.jhtml;jsessionid=GFKUCQH5S4IHNQE1GHOSKHWATMY32JVN?articleID=184405053&dept_url=/java/
>>>
>>>
>>> It turns out this is a great example for NVI. In D, we could and should do the following:
>>>
>>> class Object {
>>>     // Implement this
>>>     private bool opEqualsImpl(Object rhs) {
>>>         return false;
>>>     }
>>>     // Use this
>>>     final bool opEquals(Object rhs) {
>>>         if (this is rhs) return true;
>>>         if (this is null || rhs is null) return false;
>>>         return opEqualsImpl(rhs) && rhs.opEqualsImpl(this);
>>>     }
>>> }
>>>
>>> I took advantage of the fact that in a final function this may be null without an access violation. The implementation above ensures symmetry of equality and has each class implement a simpler primitive.
>>>
>>> What do you think?
>>
>> Eh, now after all this discussion, we're going to allow even "this" to be null? That seems like a backstep...
> 
> How is it a backstep? It's perfectly valid behavior.
> 
> Object foo;
> foo.opEquals(foo);
> 

What is the semantics of that call?

"Is something undefined equal to something undefined"? I'm not sure why that should be considered valid behavior, given that it's unanswerable.

> The call itself will *always* succeed, its when 'this' gets referenced in opEquals that the code will crash.

Or when a contract is run. Or when an inherited contract is run. Or when the method attempts to lock the synchronization object.

> 
>> Implementing opEquals as a global/static function, that calls the actual Object.opEquals virtual method would be so much more straight forward.
> 
> I agree, I prefer methods to end with Impl to stay hidden instead of being the ones to override.
> 
>> PS: I agree about the NVI thing. If you'd go to extend the language
>> for "NVI", couldn't we just introduce a second type of virtual
>> function that works this way:
>> 1. the super class' implementation is _always_ called first
>> 2. the super class function can decide to "call down" to the sub
>> class' implementation of the same method
>> => no extra do<something> method needed, and the code is (possibly)
>> clearer.
>>
>>> Andrei
> 
> I don't know how useful that would be, when you override a method you usually want to call the super method somewhere in the new implementation, not always at the beginning.
September 27, 2009
Andrei Alexandrescu wrote:
> Do you know of a precedent for this? One thing that NVI has going for it is that it's quite established - it seems to be solidly planted in programmers' lore. I was surprised to find 2.8M Google matches for the exact string "Non-Virtual Interface".

Looks like it's considered a design pattern, and design patterns are popular. I think the idea itself is relatively obvious and useful, so it's likely to encounter it often. (I didn't even know it's called "NVI".)

By the way, Aspect Oriented Programming also seems to reinvent this idea with pre/post code for methods (see Yigal's posting).
September 28, 2009
Andrei Alexandrescu Wrote:

> It turns out this is a great example for NVI. In D, we could and should do the following:
> 
> class Object {
>      // Implement this
>      private bool opEqualsImpl(Object rhs) {
>          return false;
>      }
>      // Use this
>      final bool opEquals(Object rhs) {
>          if (this is rhs) return true;
>          if (this is null || rhs is null) return false;
>          return opEqualsImpl(rhs) && rhs.opEqualsImpl(this);
>      }
> }

"I took advantage of the fact that in a final function this may be null without an access violation."

That "advantage" cannot be the general case?
Surely that statement is only true when the final function is in a base class, X, (and X can only be Object in D, right?)
for reason that the compiler can spot that the method is never overriden in ANY subclass of X (Object) , and therefore the method can be called directly rather than having to be dispatched through a VFT and consequently there is no VFT entry for that method/function.

Coming from C++ reasoning (maybe rules are subtlety different in D), the thought experiment goes like this ...

class A {
  bool opEquals(Object rhs) {print( "Hello A"); return false;}
}

class B: A {
  final bool opEquals(Object rhs) {print( "Hello B"); return false;}
}

class C: A {
}

B b = new B();
b.opEquals(obj);

prints: Hello B

C c = new C();
c.opEquals(obj);

prints: Hello A

C c;
c.opEquals(obj);

prints: An error has occurred in this application.
Please contact the program vendor for an upgrade.
Send error report to Microsoft?
Yes? No (crashing out like this is an advertised feature of the language this program was developed in)?

Just conjecture.

-- Justin Johansson

September 28, 2009
Justin Johansson wrote:
> Andrei Alexandrescu Wrote:
> 
>> It turns out this is a great example for NVI. In D, we could and should
>> do the following:
>>
>> class Object {
>>      // Implement this
>>      private bool opEqualsImpl(Object rhs) {
>>          return false;
>>      }
>>      // Use this
>>      final bool opEquals(Object rhs) {
>>          if (this is rhs) return true;
>>          if (this is null || rhs is null) return false;
>>          return opEqualsImpl(rhs) && rhs.opEqualsImpl(this);
>>      }
>> }
> 
> "I took advantage of the fact that in a final function this may be null without an access violation."
> 
> That "advantage" cannot be the general case?
> Surely that statement is only true when the final function is in a base class, X, (and X can only be Object in D, right?)
> for reason that the compiler can spot that the method is never overriden in ANY subclass of X (Object) , and therefore the method can be called directly rather than having to be dispatched through a VFT and consequently there is no VFT entry for that method/function.

Sorry, indeed I meant a "introducing final" function, not a final function. A final function that overrides one in the base class must often go through the vtable. Though if a final function (introducing or not) gets called for the static type that made it final, it needn't go through the vtable so a null this could be allowed inside of it.


Andrei