Making "super" work outside a literal?

Allen Wirfs-Brock allen at wirfs-brock.com
Tue Jun 21 08:51:46 PDT 2011


On Jun 21, 2011, at 3:28 PM, Sean Eagan wrote:

> On Sun, Jun 19, 2011 at 2:14 PM, Brendan Eich <brendan at mozilla.com> wrote:
>> On Jun 19, 2011, at 10:20 AM, Axel Rauschmayer wrote:
>> 
>> It would be nice if "super" could work in any method and not just those
>> methods that are defined inside an object literal. Then, a method would have
>> to know what object it resides in, e.g. via an implicit parameter.
>> 
>> We wish to avoid another parameter computed potentially differently for each
>> call. It will cost
> 
> What costs do you foresee?  I am not sure that there is much to
> compute.  Assume a method call O.m().  Assume "m" is found on an
> object P within O's protoype chain.  This will result in P.m being
> called, for which the "super binding" can simply be computed as
> P.[[Prototype]].  Next assume a direct function call, e.g.
> f.call(this) or f() (this === undefined), the "super binding" here
> could just be calculated as this.[[Prototype]] or undefined if |this|
> is not an object.

The call site (O.m()) does not statically know whether or not the the actual function that will be called uses 'super'. Also,  only the call site knows (dynamically) whether 'this' 'and 'super'  are different.  So, every method call site  must always be prepared for the possibility that it is will be invoking a function that uses 'super' and that the resolved 'this' and 'super' values may be different. There are two alternatively ways that a ES engine might deal with that possibility.  One way is for every call site to always pass a separate 'super' value  as an additional implicit argument and for every function to  accept to have a corresponding implicitly formal parameter. If the property lookup found an own property the values passed for 'this'' and 'super' would be the same but still must be passed as distinct values.  The other way, is for each function to have an internal property that says whether or not the function uses 'super'.  A call site, after looking up a method property could test this bit of the resolved function and only pass the implicit  'super' value to functions that actually use it. However, conditionally passing 'super' based upon whether or not the callee needs it probably has a higher execution cost than simply always passing a distinct 'super' value. So, implementations are likely to use the always pass super approach. 

So, at the very least "dynamic super" adds an extra implicit argument to every call site. This is likely to have a real performance impact because the dynamic average number of  function arguments is likely < 2 (assuming JS is similar to other languages and counting the implicit 'this' argument).  Adding an additional implicit parameter to every call would hence be near a 50% increase in argument passing overhead, on average.



> 
>> , and it will lead to surprises.
> 
> What surprises do you foresee?  I think a static |super| in light of
> ES's dynamic |this| would actually be much more surprising.  This
> would lead to looking for properties in a static |super| object that
> may be completely unrelated to the dynamic |this| value of a given
> function activation, which would certainly be surprising.

The meaning of 'super'  must be learned.  It isn't a very intuitive concept.  The 'static super' approach is essentially the same as is used in the most widely used static and dynamic object-oriented languages.

If we add a Object.defineMethod function that updates the super binding I think we will eliminate most of the other possible sources of confusion. 

> 
>> Also, anything reachable from |this| can be computed using ES5's Object.*
>> APIs.
> 
> Wouldn't the ES5 Object.* APIs be unreliable and inconvenient for such
> a calculation?  It is impossible to determine which property name a
> method m was accessed with from within m, and even if you assume a
> static property name p, climbing the prototype chain of |this| to
> determine on which object m was found via
> |Object.getOwnPropertyDescriptor(prototypeChainObject, p)| may not be
> accurate if there are any proxies in the prototype chain, or if
> anything has changed in the prototype chain since it was initially
> climbed by the engine.

All true, but what is the use case for actually doing this.  'super' is intended to address the most common static use cases. 

I suspect that the pervasive overhead of dynamic super is what has kept it out of the language up to now. I really don't think that implementations are going to look favorably upon it now.




> 
>> Quoting from
>> http://wiki.ecmascript.org/doku.php?id=harmony:object_initialiser_super :
>> 
>>  When a function contains a reference to super, that function internally
>> captures an internal reference to the [[Prototype]] of the object created by
>> the enclosing object initialiser. If such a function is subsequently
>> extracted from the original object and installed as a property value in some
>> other object, the internal reference to the original [[Prototype]] is not
>> modified. Essentially, when a function references super it is statically
>> referencing a specific object that is identified when the function is
>> defined and not the [[Prototype]] of the object from which the function was
>> most recently retrieved.
>> 
>> This behavior is consistent with that of most other languages that provide
>> reflection function to extract methods containing super and then
>> independently invoke them.
> 
> Consistency with other languages is valuable, but consistency with
> this language (ES) is vital.  A static |super| would be inconsistent
> with ES's dynamic |this|.  The semantics of |super| will be related to
> the value of |this| no matter how |super| ends up being specified, and
> thus the "super binding" should be calculated in terms of the "this
> binding" if intra-language consistency is to be kept.

ES 'this' is no more dynamic than 'this' in any other OO language. What is somewhat unique to ES is how easy it is to detach a method from one inheritance hierarchy and inject it into another inheritance hierarchy. That is what impacts the 'super'.

I agree that there is at least a small bug farm hazard here. However, think that we do things to lower the hazard and I think that go practice will generally minimize the hazard.  In addition, I think that the other extensions that have reached proposal status will lead to a more declarative style of ES program definition that will generally reduce the need and practice of imperative class-like abstraction construction of the sort that might run into this hazard.

Allen


More information about the es-discuss mailing list