www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - opEquals and the Non-Virtual Interface idiom

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 27 2009
next sibling parent reply grauzone <none example.net> writes:
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=18440
053&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

Sep 27 2009
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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=18440
053&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
Sep 27 2009
next sibling parent Yigal Chripun <yigal100 gmail.com> writes:
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 } }
Sep 27 2009
prev sibling parent grauzone <none example.net> writes:
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).
Sep 27 2009
prev sibling parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
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=18440
053&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.
Sep 27 2009
parent downs <default_357-line yahoo.de> writes:
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.

Sep 27 2009
prev sibling parent reply Justin Johansson <procode adam-dott-com.au> writes:
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
Sep 28 2009
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 28 2009