Making "super" work outside a literal?

Brendan Eich brendan at mozilla.com
Sun Jun 26 09:52:40 PDT 2011


On Jun 26, 2011, at 5:09 AM, Allen Wirfs-Brock wrote:

> 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.

I know, and I'm not endorsing every syllable, but the interview is worth reading and I humbly suggest that it put some arrows into one or two practical pain-point targets.


> 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.

There's a pragmatic or real-world observation in that interview, which I didn't cite. The "incoming" (wrong word but close enough) contract of a method, its Hoare preconditions/postconditions, is almost always better understood than the "outgoing" contract: how its implementation depends on peer or other methods in or above (or below!) the abstraction in the class hierarchy. And virtual by default raises the risks of getting the latter wrong.

Peter Michaux raised this already in talking about "sideways" calls from a superclass method to its peer methods in that class, not to be overridden.

In C++, if a superclass explicitly declares a method virtual, perhaps even pure virtual, intending that subclasses implement it as a callback or "plugin API", then virtual is necessary or it's back to C function pointers. This is a clear counter-example to any "always use sideways calls among superclass methods that call one another" dogma. Such a plugin method must be delegated to the subclass, i.e., virtual in C++.

Of course, we don't need dogma. All we really need IMHO is the right default, and acknowledgement that practical concerns require both overrideable and sideways calls so the syntax for both should be about as usable.


> 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/ 

Thanks, will take a look.

Has it helped the masses get this right? Anders' point about Java class library updates always breaking the extensions in the wild rang true from what I remember.


>> 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 

Sure, people need to avoid complex object models as APIs in general. For implementations they may be manageable. But we don't have interfaces (proxies whether hand-coded classes or capitacal-P Proxies are too expensive).

Giving users a facet or shard of your abstraction without having to write a proxy may be the next shoe to drop after classes. Private name objects may help but the syntax isn't there (on purspose).


> 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.

Did Java user experience seem to go the other way from Smalltalk, in your view? If so, any ideas why?


>> 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.

But we don't have convenient syntax for sideways calls. Unless I'm missing something, they are darn inconvenient, requiring a closure and some refactoring into private helpers to share code.


>> 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);
> });

Why wouldn't this.hashCode suffice? There's no hashCode override, IIUC.


>> 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.

My point is not about utility, since people find all manner of practices useful.

My point is that super.bar from method foo always goes up the inheritance hierarchy, but superclass foo's calls to this.bar will go back down if the subclass overrides bar. Unless superclass foo uses a sideways call, which is too painful to be likely.

Should we consider making sideways bar calls about as convenient as this.bar calls?


>  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);

I'm frankly not sure about the value of this use-case, compared to methods in classes and methods in initialisers.

To retarget super with Object.defineMethod, you have to know a *lot* about both the function, and the target object and its prototype chain. This is a hard case. It will be rare. It could be useful. It could also be a source of woe.

On the plus side, I see the coherence of free super in any function not in a class or literal combined with Object.defineMethod. It has that low-level appeal that we often seek to improve the language without over-designing in advance of user testing. It fills the "super gap" in a maximally programmable way, with syntax for super that avoids most of the pitfalls. People may build on it in ways we don't foresee.

But it needs user testing too.

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


More information about the es-discuss mailing list