Making "super" work outside a literal?
seaneagan1 at gmail.com
Tue Jun 21 11:04:45 PDT 2011
On Tue, Jun 21, 2011 at 10:51 AM, Allen Wirfs-Brock
<allen at wirfs-brock.com> wrote:
> 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.
I disagree that a dynamic super would lead to a '50% increase in
argument passing overhead, on average'. First, there is the implicit
'arguments' argument, which is an object that actually needs to be
created per call site, not just a binding, and thus is presumably much
more expensive. Second, 'argument passing overhead' involves sunk
costs that are not dependent on the number of argument bindings that
need to be created. Third, implicit arguments, as 'super' would be,
should be less costly than explicit arguments since the argument value
does not need to be resolved.
A better estimate of the overhead of dynamic super would probably be
the overhead of an implicit 'this' binding, i.e. the cost of creating
a 'this' binding for a function activation that not involving an
explicit 'this' via Function.prototype.call or
Function.prototype.apply, or Function.prototype.bind. Does anyone
have any estimates of this cost, either absolute or relative to some
>>> , 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.
The 'static super' approach is easier to understand, my argument is
that the 'dynamic super' approach is easier to actually *use*, at
least within ES.
> 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.
That's not very convenient though compared to simple assignment, and I
don't think it composes with Object.defineProperty, meaning you cannot
have control of the property descriptor attributes for methods defined
by Object.defineMethod. Also, it doesn't address dynamically adding
accessor property getters and setters that use 'super'.
>>> Also, anything reachable from |this| can be computed using ES5's Object.*
>> 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 believe there are many valid dynamic use cases for super (see
below), that may even be every bit as common as some of the static use
cases. Why is 'super' intended to only address the static ones?
> 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.
I was unaware that dynamic super had been discussed, proposed, or
attempted previously for ES, or that it was ever included to have
'pervasive overhead'. Do you any references for this?
>>> 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'.
Yes, this is the basic reason why I believe 'dynamic super' is needed,
because methods and accessor property getters and setters can be
dynamically added to objects in ES, which is a very common practice.
> 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.
I agree that object literal extensions will lessen the need for
dynamic super, however, I think there will still be many important use
cases for it. Here are a few I can think of:
* Be able to define methods and accessor property getters and setters
that use 'super' on types of objects that cannot be created with an
object literal, e.g. Arrays, Functions, etc.
* Be able to define accessor property getters and setters that use
'super' via Object.defineProperty rather than the static object
literal getter / setter syntax.
* Reuse methods that use 'super' within multiple inheritance heirarchies.
* Creating a copy of an object which has methods that use 'super'. In
calls to the methods from the new object, 'super' should refer to the
new object, not the original.
* Callback functions dynamically added to objects or passed to
constructors could benefit from using 'super'.
* Reflection functions that can use 'super' with an arbitrary 'this' binding.
More information about the es-discuss