digitalmars.D - opEquals and the Non-Virtual Interface idiom
- Andrei Alexandrescu (21/21) Sep 27 2009 Here's an article about the perils of equals in Java (opEquals in D):
- grauzone (12/39) Sep 27 2009 Eh, now after all this discussion, we're going to allow even "this" to
- Andrei Alexandrescu (10/50) Sep 27 2009 It's also less safe because people could call the incomplete primitive
- Yigal Chripun (15/66) Sep 27 2009 This is a smalltalk idea -
- grauzone (6/10) Sep 27 2009 Looks like it's considered a design pattern, and design patterns are
- Jeremie Pelletier (11/53) Sep 27 2009 How is it a backstep? It's perfectly valid behavior.
- downs (4/65) Sep 27 2009 What is the semantics of that call?
- Justin Johansson (29/44) Sep 28 2009 "I took advantage of the fact that in a final function this may be null ...
- Andrei Alexandrescu (7/30) Sep 28 2009 Sorry, indeed I meant a "introducing final" function, not a final
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
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
grauzone wrote:Andrei Alexandrescu wrote:Good point.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.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
On 27/09/2009 19:01, Andrei Alexandrescu wrote:grauzone wrote: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 } }Andrei Alexandrescu wrote:Good point.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.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
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
grauzone wrote:Andrei Alexandrescu wrote: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.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.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.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.Andrei
Sep 27 2009
Jeremie Pelletier wrote:grauzone wrote: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.Andrei Alexandrescu wrote:How is it a backstep? It's perfectly valid behavior. Object foo; foo.opEquals(foo);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...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.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.Andrei
Sep 27 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
Sep 28 2009
Justin Johansson wrote:Andrei Alexandrescu Wrote: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. AndreiIt 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.
Sep 28 2009