Making "super" work outside a literal?

Allen Wirfs-Brock allen at wirfs-brock.com
Sun Jun 26 05:09:31 PDT 2011


On Jun 25, 2011, at 10:42 PM, Brendan Eich wrote:

> 
> There are deeper waters here. See http://www.artima.com/intv/nonvirtualP.html (via http://geekswithblogs.net/madhawa/archive/2006/09/17/91418.aspx) where Anders Hejlsberg says:
> 
> "There are two schools of thought about virtual methods. The academic school of thought says, "Everything should be virtual, because I might want to override it someday." The pragmatic school of thought, which comes from building real applications that run in the real world, says, "We've got to be real careful about what we make virtual."
> When we make something virtual in a platform, we're making an awful lot of promises about how it evolves in the future. For a non-virtual method, we promise that when you call this method, x and y will happen. When we publish a virtual method in an API, we not only promise that when you call this method, x and y will happen. We also promise that when you override this method, we will call it in this particular sequence with regard to these other ones and the state will be in this and that invariant.

I have to say that I violently disagree with Anders' position on this and his characterization of "everything virtual" as representing an academic perspective.  The experience in the Smalltalk community (which is about as pragmatic as you can get...remember Smalltalk was once billed as the "Successor to COBOL") is that the ability to always over-ride an inherited method is extremely useful. Also note that Anders was trying to position C# as superior to Java by suggesting that Java (where everything is virtual) is too academic in its origins.

The root of this argument is really about open vs. closed system extensibility.  A closed system may be extensible, but only in certain predetermined manners.  A totally open system can be extended in unanticipated manners. The Smalltalk experience is that attempts to predict or pre-limit how a class might be reused or extended are seldom correct. It is better to simply have everything open and leave it up to future extenders to decide what they need to do.

That said there are techniques that enhance the reliable extensibility of classes.  See:
www.wirfs-brock.com/PDFs/des_ext_classes.pdf
http://www2.parc.com/csl/groups/sda/publications/papers/Kiczales-OOPSLA92/ 

> 
> Every time you say virtual in an API, you are creating a call back hook. As an OS or API framework designer, you've got to be real careful about that. You don't want users overriding and hooking at any arbitrary point in an API, because you cannot necessarily make those promises. And people may not fully understand the promises they are making when they make something virtual."

Which is one of the reasons why complex object models are a bad way to design stable OS APIs, as was discovered by the designers of Windows Longhorn and many others.  That is not a use case we should be contemplating for ES.  See http://www.wirfs-brock.com/allen/posts/379 



> 
> The rest is well worth reading.
> 
> If subclass foo can call super.bar() and the superclass bar calls this.foo(), what happens? Java without final before the method is like C++ with virtual before the method. The subclass foo() will be called.

In a completely open class inheritance based system, every method is essentially a template method (http://www.oodesign.com/template-method-pattern.html ).  This creates potential hazards but it also creates many opportunities for unanticipated reuse and extensibility.  In the Smalltalk experience, the positive value of the latter typically far exceeded the negative value of the former.

> 
> Often, especially if you buy Anders' arguments, you want what Peter Michaux has called a sideways call from the superclass bar, to lexical foo(). You can do that and prevent overrides from subclasses. This is not always the right thing, but it seems like the right default.

Exactly.  The way to avoid the "fragile superclass" problem is for methods that feel the need to protect themselves to only use lexically bound calls for procedural decomposition rather than method calls.  Every time a method makes a this call it is explicitly delegating to a potentially unknown implementation. If that is a concern, don't do it.


> 
> I asked Jeremy Ashkenaz about CoffeeScript's super()-only restriction, modeled on Ruby. He wrote back:
> 
> "The rationale [is] that if you're overriding an implementation of a method, you're responsible for preserving the proper API at that point. By reaching behind the back of an overridden function, and calling its parent implementation without going through it, you're saying that the override was done wrong, or doesn't preserve the API you need.
> 
> This is basically the difference between Java super and Ruby super.
> 
> I'd be very curious to see an example of an inheritance pattern that relies on being able to call super() against "other" methods."

The classic example is when you have groups of methods that need to be over-ridden in a consistent manner. 

For example, hash based data structures typically impose a constraint upon equality and hash methods such that items that compare equal much exhibit the same hash values.  Hence you might see a method such as:

Object.defineMethod(obj, "equal", functionl(another) {
      // in this abstraction hashCode is fast but equal is slow, so use hash/equal invariant to optimize equal
      if (super.hashCode != another.hashCode) return false;
      return super.equal(another);
});

> ...
> It seems to me that we missed something in jumping on the Java (or was it C#? But C# has non-virtual, or Java final, methods by default, unlike Java) bandwagon with the super.foo or super.bar() from foo design.

It's not clear to me what you think we missed. The super call to the same name is by far the most common case.  Probably >95%.  However, the super call to a different name does have utility.  In addition, because of the weak association between a method (property) name and actual function bodies the name following super arguably provides useful redundancy concerning the actual intent of the function.  Consider something like the following:


let f = function() {super.foo()+1};  //clear intent
let g= function() {super()+1};   //as a reader, how to I interpret the original authors intent?

let o = sup <| {};
Object.defineMethod(o,"foo",f);
Object.defineMethod(p,"bar", g);


Allen


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110626/f05b653e/attachment.html>


More information about the es-discuss mailing list