Making "super" work outside a literal?
On Sun, Jun 19, 2011 at 10:20 AM, Axel Rauschmayer <axel at rauschma.de> 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.
So you want "super" to be dynamic but tied to the value of "this" inside that function? Something like...
function fn() { return this.alpha + super.beta(); }
function Foo() {} Foo.prototype.alpha = function() {return 1;}; Foo.prototype.beta = function() {return 2;};
function Bar() {} Bar.prototype = Object.create(Foo.prototype); Bar.prototype.beta = function() {return 5;};
var b = new Bar(); fn.call(b); // 1 + 2 = 3
Peter
Exactly!
One way to do this is to give methods an implicit parameter "here" (the object in which a method was found during dispatch), in addition to "this".
Then super.foo(x, y) would desugar to Object.getPrototypeOf(here).foo.call(this, x, y)
With call() and apply(), you would have here === this.
Axel
On Sun, Jun 19, 2011 at 10:56 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
Exactly!
One way to do this is to give methods an implicit parameter "here" (the object in which a method was found during dispatch), in addition to "this".
In some variant, "here" might make it possible to do what I was describing as "sideways" calls?
www.mail-archive.com/[email protected]/msg08069.html
Then super.foo(x, y) would desugar to Object.getPrototypeOf(here).foo.call(this, x, y)
With call() and apply(), you would have here === this.
When the prototype chain is three or more objects long, I'm wondering what would happen.
With "here" I'd expect and think people would expect the following behavior (but I don't think it is what your proposal of "here" would do.)
function Alpha() {} Alpha.prototype.one = function() {return 'Alpha.one';}; Alpha.prototype.two = function() {return 'Alpha.two';}; Alpha.prototype.three = function() {return super.one() + ' ' + here.one() + ' ' + this.one();};
function Beta() {} Beta.prototype = Object.create(Alpha.prototype); Beta.prototype.one = function() {return 'Beta.one';}; Beta.prototype.two = function() {return super.one() + ' ' + here.one()
- ' ' + this.one();};
function Gamma() {} Gamma.prototype = Object.create(Beta.prototype); Gamma.prototype.three = function() {return super.one() + ' ' + here.one() + ' ' + this.one();};
var a = new Alpha(); var b = new Beta(); var g = new Gamma();
a.three(); // "Alpha.one Alpha.one Alpha.one" b.two(); // "Alpha.one Beta.one Beta.one" g.two(); // "Alpha.one Beta.one Gamma.one" g.three(); // "Beta.one Gamma.one Gamma.one"
Peter
Note that "this" always points to where the search for a method starts (the “receiver” of the method call), there (current) beginning of the prototype chain. "here" points to where a method has been found. This is a static aspect, except that turning it into a property of a function would make it impossible to put the same function into multiple objects.
Then super.foo(x, y) would desugar to Object.getPrototypeOf(here).foo.call(this, x, y)
With call() and apply(), you would have here === this.
When the prototype chain is three or more objects long, I'm wondering what would happen.
With "here" I'd expect and think people would expect the following behavior (but I don't think it is what your proposal of "here" would do.)
function Alpha() {} Alpha.prototype.one = function() {return 'Alpha.one';}; Alpha.prototype.two = function() {return 'Alpha.two';}; Alpha.prototype.three = function() {return super.one() + ' ' + here.one() + ' ' + this.one();};
function Beta() {} Beta.prototype = Object.create(Alpha.prototype); Beta.prototype.one = function() {return 'Beta.one';}; Beta.prototype.two = function() {return super.one() + ' ' + here.one()
- ' ' + this.one();};
function Gamma() {} Gamma.prototype = Object.create(Beta.prototype); Gamma.prototype.three = function() {return super.one() + ' ' + here.one() + ' ' + this.one();};
var a = new Alpha(); var b = new Beta(); var g = new Gamma();
Wow, quite a puzzle. I hope I get it right:
a.three(); // "Alpha.one Alpha.one Alpha.one"
super.one() would cause an exception, because method "three" is found in Alpha.prototype (=> becomes "here") whose prototype is Object.prototype, which does not have a method one().
b.two(); // "Alpha.one Beta.one Beta.one"
Would work. The search for super.one() would start in Alpha.prototype, the search for here.one() would start in Beta.prototype, the search four this.one() would also start in Beta.prototype.
g.two(); // "Alpha.one Beta.one Gamma.one"
Is OK, only "this" is different, "here" is still Beta.prototype.
g.three(); // "Beta.one Gamma.one Gamma.one"
I dont’t see "Gamma.one" anywhere.
I would say: "Beta.one Beta.one Beta.one"
Maybe you can condense this into a simpler example that gets to the core of where you think things might be tricky.
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, and it will lead to surprises.
Also, anything reachable from |this| can be computed using ES5's Object.* APIs.
Quoting from harmony:object_initialiser_super :
Makes sense.
I don’t know how problematic this would be, but if "super" is an internal property of a function, then it could be updated whenever such a function is set as the value of a property.
I’m increasingly decomposing object literals into assignments to properties, especially if a literal is long (easier rearrangement of methods, because there is no need to avoid the trailing comma; context is clearer).
Axel
On Jun 19, 2011, at 12:54 PM, Axel Rauschmayer wrote:
Makes sense.
I don’t know how problematic this would be, but if "super" is an internal property of a function, then it could be updated whenever such a function is set as the value of a property.
No, that doesn't work. Both for cases where the function author really did mean the proto-object of the literal-induced object surrounding the function, and in efficiency terms (we do not mutate the RHS of assignment depending on the LHS's Reference Base, e.g.).
If you really want to change what super means in a function expressed in an object initialiser, I'd like to see the exact use-case.
I’m increasingly decomposing object literals into assignments to properties, especially if a literal is long (easier rearrangement of methods, because there is no need to avoid the trailing comma; context is clearer).
Assignments to properties is often more expensive, though (I know the Closure library prefers this style but it is the exception).
Trailing comma in object initialiser is fine in ES5-conforming engines.
I don’t know how problematic this would be, but if "super" is an internal property of a function, then it could be updated whenever such a function is set as the value of a property.
No, that doesn't work. Both for cases where the function author really did mean the proto-object of the literal-induced object surrounding the function, and in efficiency terms (we do not mutate the RHS of assignment depending on the LHS's Reference Base, e.g.).
If you really want to change what super means in a function expressed in an object initialiser, I'd like to see the exact use-case.
The only use case would be assigning a function that uses "super" to a property.
On Jun 19, 2011, at 7:32 PM, Axel Rauschmayer wrote:
I don’t know how problematic this would be, but if "super" is an internal property of a function, then it could be updated whenever such a function is set as the value of a property.
No, that doesn't work. Both for cases where the function author really did mean the proto-object of the literal-induced object surrounding the function, and in efficiency terms (we do not mutate the RHS of assignment depending on the LHS's Reference Base, e.g.).
If you really want to change what super means in a function expressed in an object initialiser, I'd like to see the exact use-case.
The only use case would be assigning a function that uses "super" to a property.
Such an assignment might need the meaning of super to remain the same, though. What's the use-case for remapping it? Stealing methods from one class to another whose static super is not the same object as the proto of the object into which the stolen function reference is stored?
If you really want to change what super means in a function expressed in an object initialiser, I'd like to see the exact use-case.
The only use case would be assigning a function that uses "super" to a property.
Such an assignment might need the meaning of super to remain the same, though. What's the use-case for remapping it? Stealing methods from one class to another whose static super is not the same object as the proto of the object into which the stolen function reference is stored?
It wouldn’t be about stealing, but about setting up an object differently:
var obj = { foo: function (x) { super.foo(x); } };
=== VERSUS ===
var obj = {}; obj.foo = function (x) { super.foo(x); };
The <| helps to make that less urgent (I often use Object.create() and then add properties via assignment, to avoid the verbosity of property descriptors).
On Jun 19, 2011, at 9:13 PM, Axel Rauschmayer wrote:
It wouldn’t be about stealing, but about setting up an object differently:
var obj = { foo: function (x) { super.foo(x); } };
=== VERSUS ===
var obj = {}; obj.foo = function (x) { super.foo(x); };
The <| helps to make that less urgent (I often use Object.create() and then add properties via assignment, to avoid the verbosity of property descriptors).
The assignment form would not work the same way.
Indeed I hope super usage would be an early error in a function that is not directly contained in an ObjectLiteral.
I actually have a solution in mind for this.
Under the current proposal, a method containing super is "statically bound" to a specific object that provides the [[Prototype]] base for the "super" property lookup. This is normally done by defining the method in the context of an object literal or class declaration. If you want to define a method containing outside such a context you still need to provide the necessary binding.
What I have in mind is adding a reflection function for doing that:
let f= function () {return super.f()}; f(); //TypeError -- super unbound
let sup = {foo() {return "super hello"}; let sub = sup <| {};
Object.defineMethod(sub,"foo",f}; //adds f as property named "foo" of sub. binds super for f
f(); // super hello
with corrections below:
Brendan Eich-3 wrote:
Quoting from 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.
It's most sane proposal I think. However few things are not obvious to me, will following evaluate as I assume:
var A = { one: function () { return 'A.foo'; } }; var B = Object.create(A);
var C = Object.create(B); C.one = function () { return super.one(); };
var c1 = Object.create(C); obj.one(); // 'A.foo'
B.two = function () { this.three(); }; B.three = function () { return 'B.three'; }; C.two = function () { super.two(); }; C.three = function () { return 'C.three'; };
var c2 = Object.create(C); c2.two(); // C.three
Thanks!
Mariusz Nowak
I have also been working on a proposal to extend "super" to be available anywhere by using a "super binding" concept similar to the exising "this binding" concept, as well as taking ideas from [object initialiser super]. It is a work in progress, any comments would be much appreciated!
Please see: gist.github.com/1036200
[object initialiser super] harmony:object_initialiser_super
It doesn't seem quite right that an upward call like
Object.getPrototypeOf(here).foo.call(this)
has sugar
super.foo()
but sideways calls like
here.foo.call(this)
don't have any sugar.
What is the use case for sideways calls? Can you point me to an example? If you are after data that is private to a given prototype, consider using an IIFE, instead.
There are various interesting ideas in other programming languages when it comes to combining methods while overriding:
- Cooperative methods: force overriding methods to call them.
- Before/after/around methods: are invoked before/after/before+after a method that they override: www.cs.cmu.edu/Groups/AI/html/cltl/clm/node285.html
But that would be overkill for ES.next.
By the way, I like this idea that "super" is available all the time (not just in an initializer) like "this" is always available; however, adding another implicit variable "here" which is dynamic like "this" is disconcerting as "this" has been quite a wild beast in JavaScript to say the least.
I framed things in terms of |here|, because that value can be easily produced, as a byproduct of looking for a property. It might also, some day, give us the ability to set a property that is not at the beginning of a property chain. For now, programmers would never directly see |here|, only |super|. |super| would be computed on demand, only when someone asks for that value.
Right. Sorry if I added to the confusion. My explanation was along the lines: “If there is a dynamic |super|, then...”. The lexical |super| is perfectly adequate.
I’d imagine it would work something like this (modulo some syntactic sugar):
var Super = {}; Super.foo = function() { return "Super: "+this.id; };
var Sub = Object.create(Super); Sub.foo = function me() { return "Sub "+me.super.foo.call(this); }; // The following assignment would normally be made by an inheritance API Sub.foo.super = Object.getPrototypeOf(Sub);
var s = Object.create(Sub); s.id = "fcb3"; console.log(s.foo()); // Sub Super: fcb3
On Mon, Jun 20, 2011 at 3:23 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
What is the use case for sideways calls? Can you point me to an example?
You want to allow the API (a.k.a. public methods) of an object to be overridden, but you don't want the functionality of any non-overidden API methods to change. In short, you want to avoid the template pattern. I gave a synthetic example in the sideways calls thread I started.
If you are after data that is private to a given prototype, consider using an IIFE, instead.
There are various interesting ideas in other programming languages when it comes to combining methods while overriding:
- Cooperative methods: force overriding methods to call them.
- Before/after/around methods: are invoked before/after/before+after a method that they override: www.cs.cmu.edu/Groups/AI/html/cltl/clm/node285.html
But that would be overkill for ES.next.
By the way, I like this idea that "super" is available all the time (not just in an initializer) like "this" is always available; however, adding another implicit variable "here" which is dynamic like "this" is disconcerting as "this" has been quite a wild beast in JavaScript to say the least.
I framed things in terms of |here|, because that value can be easily produced, as a byproduct of looking for a property. It might also, some day, give us the ability to set a property that is not at the beginning of a property chain.
Perhaps useful in combination with noSuchMethod. More efficient to dynamically generate a method on the "here" object once then on all the "this" inheriting objects one-by-one.
Peter
Peter Michaux wrote:
On Mon, Jun 20, 2011 at 3:23 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
What is the use case for sideways calls? Can you point me to an example?
You want to allow the API (a.k.a. public methods) of an object to be overridden, but you don't want the functionality of any non-overidden API methods to change. In short, you want to avoid the template pattern. I gave a synthetic example in the sideways calls thread I started.
Peter,
I'm actually not sure about that. I would even say that allowing such sideway calls within 'instance' methods would be bad hint. I wouldn't like to approach following code, it doesn't allow to extend A the way I may need to:
var A = function () {}; A.prototype = { one: function () { return A.prototype.two.call(this, args...); }, two: function () { ... } };
If we definitely need to call A.prototype.two whether we extended A or not, then we probably talk about 'static' method which should not be called on instance and should be defined directly on A (or privately within closure), then we may just call it A.two(args...). At least that's what my OOP experience says.
I understand your example at old.nabble.com/super%2C-self%2C-and-sideways---durable-vs.-template-p31830714.html to me it's just programmer mistake that should throw error. Maybe you're looking for some pattern that can be realized using 'static' methods (?) but forcing or introducing such behavior somewhere close 'this' or 'super' logic might not be what people expect. My experience is not that vast, is there any language that provide sugar for such calls ?
Mariusz Nowak
From: Mariusz Nowak <medikoo+mozilla.org at medikoo.com> Date: June 21, 2011 10:54:29 GMT+02:00
From my perspective, this proposal looks perfect. I wouldn't look further :)
I agree. Object literal extensions are all very nice.
Note that (as Brendan mentioned) |here| (which I used to explain things) would be a static value, not a dynamic one (unlike |this|). Or (a static) |super| might just be used directly.
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.
, 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.
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.
Quoting from 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.
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.
This may already be what you are suggesting, so to clarify:
I would introduce a binding called "here" that points to the object P where m has been found.
- Method that uses super: compute super as here.[[Prototype]]
- Non-method function that uses super: "here" is undefined, using super causes an error.
If super is not used there is little computational cost! (Other than a binding per function call which may still be prohibitive.)
On Tue, Jun 21, 2011 at 9:40 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
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.
This may already be what you are suggesting, so to clarify:
I would introduce a binding called "here" that points to the object P where m has been found.
- Method that uses super: compute super as here.[[Prototype]]
- Non-method function that uses super: "here" is undefined, using super causes an error.
If super is not used there is little computational cost! (Other than a binding per function call which may still be prohibitive.)
Regardless of any "here" binding, "super" would only need to be bound within functions that actually use it (the same should be true for |this|). However, as I mentioned, it seems the cost of a dynamic |super| binding should be relatively low, comparable to the cost of a dynamic |this| binding.
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 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.
On Jun 21, 2011, at 4:13 PM, Sean Eagan wrote:
On Tue, Jun 21, 2011 at 9:40 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
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.
This may already be what you are suggesting, so to clarify:
I would introduce a binding called "here" that points to the object P where m has been found.
- Method that uses super: compute super as here.[[Prototype]]
- Non-method function that uses super: "here" is undefined, using super causes an error.
If super is not used there is little computational cost! (Other than a binding per function call which may still be prohibitive.)
Regardless of any "here" binding, "super" would only need to be bound within functions that actually use it (the same should be true for |this|). However, as I mentioned, it seems the cost of a dynamic |super| binding should be relatively low, comparable to the cost of a dynamic |this| binding.
No, dynamic 'super' is an additional overhead that is the same as the dynamic 'this' overhead. Dynamic 'super' essentially doubles the dynamic 'this' overhead on every call.
On Tue, Jun 21, 2011 at 1:55 AM, Mariusz Nowak <medikoo+mozilla.org at medikoo.com> wrote:
Peter Michaux wrote:
You want to allow the API (a.k.a. public methods) of an object to be overridden, but you don't want the functionality of any non-overidden API methods to change. In short, you want to avoid the template pattern. I gave a synthetic example in the sideways calls thread I started.
I'm actually not sure about that. I would even say that allowing such sideway calls within 'instance' methods would be bad hint. I wouldn't like to approach following code, it doesn't allow to extend A the way I may need to:
var A = function () {}; A.prototype = { one: function () { return A.prototype.two.call(this, args...); }, two: function () { ... } };
Yes it may seem inconvenient but it is a decision for the the person creating the class, not the person trying to extend the class.
Peter
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 other cost?
, 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.* 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 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 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.
Allen
Thanks, Sean Eagan
I'm answering some of the performance questions in this, but i don't know what the intended semantics of 'dynamic super' would be so I can't give specific details of most of the use cases. If it's essentially sugar for super.foo => Object.getPrototypeOf(this).foo (with appropriate munging for calls, etc) then the cost is at point of use only, but if it's anything more fun (there are references to additional implicit parameters below which would be fairly bad for performance of calls)
On Jun 21, 2011, at 11:04 AM, Sean Eagan wrote:
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.
Nope, creating an arguments object is on average free, as most uses can be lowered.
Second, 'argument passing overhead' involves sunk costs that are not dependent on the number of argument bindings that need to be created.
Every additional argument has a performance cost
Third, implicit arguments, as 'super' would be, should be less costly than explicit arguments since the argument value does not need to be resolved.
I haven't really been following this but why does super need to be passed as an additional parameter?
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 other cost?
That depends on the exact use case. JSC doesn't determine what |this| is at the callsite (In general strict mode makes that impossible), so it becomes a question of whether you use it or not.
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?
No idea -- it would depend on what the actual semantics of super were
On Jun 21, 2011, at 11:33 AM, Oliver Hunt wrote:
I'm answering some of the performance questions in this, but i don't know what the intended semantics of 'dynamic super' would be so I can't give specific details of most of the use cases. If it's essentially sugar for super.foo => Object.getPrototypeOf(this).foo (with appropriate munging for calls, etc) then the cost is at point of use only, but if it's anything more fun (there are references to additional implicit parameters below which would be fairly bad for performance of calls)
That's the point: super must mean something other than Object.getPrototypeOf(this). Otherwise with the prototypal pattern you will infinitely recurse calling the same prototype-homed foo from foo via super.foo().
If super was shorthand only for Object.getPrototypeOf(this), then there's hardly any point in making super short for that expression.
Axel's notion of a |here| object helps, in that Object.getPrototypeOf(here) is more what you want from 'super' -- but not necessarily when the method has been borrowed by another object used as |here|. Note the "necessarily" -- you may know how to rebind 'super', but you may not. Allen's Object.defineMethod requires an affirmative defense before you can rebind super. It's a safety step, since 'super' is an internal function property and not a free parameter.
BTW I do not agree we can or should try to reserve 'here' or expose the method's "home object" -- that breaks abstractions built using prototypes. 'super' does not, because it always goes to the [[Prototype]] of the object in which the method was defined.
On Jun 21, 2011, at 11:53 AM, Brendan Eich wrote:
On Jun 21, 2011, at 11:33 AM, Oliver Hunt wrote:
I'm answering some of the performance questions in this, but i don't know what the intended semantics of 'dynamic super' would be so I can't give specific details of most of the use cases. If it's essentially sugar for super.foo => Object.getPrototypeOf(this).foo (with appropriate munging for calls, etc) then the cost is at point of use only, but if it's anything more fun (there are references to additional implicit parameters below which would be fairly bad for performance of calls)
That's the point: super must mean something other than Object.getPrototypeOf(this). Otherwise with the prototypal pattern you will infinitely recurse calling the same prototype-homed foo from foo via super.foo().
Example:
C.prototype = { foo() { addedValue(); return super.foo(); } ... };
x = new C; x.foo(); // oops, diverges in endless self-recursion.
Now, with the static 'super' semantics plus Object.defineMethod(O, 'foo', C.prototype.foo), you can rebind 'super'.
One thing I'm not sure about: whether Object.defineMethod should mutate C.prototype.foo's internal property, or make a new function object with a different 'super' binding.
In any case, the static and internal-to-method semantics avoid the infinite recursion hazard, and they avoid the cost of an extra parameter beyond |this|.
On Jun 21, 2011, at 9:55 PM, Brendan Eich wrote:
One thing I'm not sure about: whether Object.defineMethod should mutate C.prototype.foo's internal property, or make a new function object with a different 'super' binding.
I'm still thinking about this but I'm leaning towards specifying that defineMethod creates a new function and leave it up to implementations to optimize trivial cases where that is unnecessary like: Object.defineMethod(obj, "foo", function () {super.foo()});
Also, Object.defineProperty used to define a accessor property get or set function probably should also have this semantics. Because 'super' is new that seems like an upwards compatible extension.
On Jun 22, 2011, at 2:28 AM, Allen Wirfs-Brock wrote:
On Jun 21, 2011, at 9:55 PM, Brendan Eich wrote:
One thing I'm not sure about: whether Object.defineMethod should mutate C.prototype.foo's internal property, or make a new function object with a different 'super' binding.
I'm still thinking about this but I'm leaning towards specifying that defineMethod creates a new function and leave it up to implementations to optimize trivial cases where that is unnecessary like: Object.defineMethod(obj, "foo", function () {super.foo()});
+1, or more
Also, Object.defineProperty used to define a accessor property get or set function probably should also have this semantics. Because 'super' is new that seems like an upwards compatible extension.
Yes.
There is no infinite recursion hazard in the semantics that Axel suggested, and that I used in the gist I posted (gist.github.com/1036200), where |super| is essentially the "the [[Prototype]] internal property of the object on which the method or accessor getter or setter was found" except that the |this| used in method calls and accessor getters and setters accessed from |super|, e.g. super.method() or super.accessorWithGetterOrSetterThatUseSuper, is the |this| of the caller, rather than the |super| of the caller.
I don't think we need any "safety check" when assigning a method or accessor getter or setter that uses super to an object. The concept of "super" seems to be a relative one, it is the object one step up in the prototype chain from the current object. If a method or accessor getter or setter needs an absolute reference to the prototype of a given object, it should refer to it by name.
Also, AFAICT there is no performance cost of a dynamic super beyond the performance cost of static super. If there would be any difference between the two, it would be in the work that needs to be done to determine what the |super| binding of a particular call site should be. In either the dynamic or static case, the engine will climb the prototype chain when looking for a method or accessor property. When it finds one that matches, in the dynamic case the |super| binding will just be the [[Prototype]] internal property of the current prototype chain object, in the static case it will be the [[???]] internal property of the method or accessor getter or setter found. Thus, in both cases it will just be a matter of accessing an internal property of an object that is immediately accessible when climbing the prototype chain.
On Wed, Jun 22, 2011 at 4:28 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Jun 21, 2011, at 9:55 PM, Brendan Eich wrote:
One thing I'm not sure about: whether Object.defineMethod should mutate C.prototype.foo's internal property, or make a new function object with a different 'super' binding.
I'm still thinking about this but I'm leaning towards specifying that defineMethod creates a new function and leave it up to implementations to optimize trivial cases where that is unnecessary like: Object.defineMethod(obj, "foo", function () {super.foo()});
Creating extra function objects is a cost that would be avoided by a dynamic super approach. Why would it not be necessary in that "trivial case"? Do you mean to say that |Object.defineMethod(o, "method", method).method !== method| ? That would be quite surprising to a user, especially if it is conditionally true depending on whether |method| is an optimizable "trivial case".
As I mentioned earlier Object.defineMethod is non-orthogonal in that it doesn't allow you to specify any property descriptor attributes for methods, you would be stuck with whatever the default values would be for methods.
Also, you haven't addressed the |Object.defineMethod(proxy, "method", method)| case.
Also, Object.defineProperty used to define a accessor property get or set function probably should also have this semantics. Because 'super' is new that seems like an upwards compatible extension.
This is inconsistent. You are are not re-binding super for |Object.defineProperty(o, "method", {value: methodThatUsesSuper})| but you are for |Object.defineProperty(o, "property", {get: getterThatUsesSuper, set: setterThatUsesSuper})|.
This also doesn't address |Object.defineProperty(o, "method", {get: getterThatReturnsMethodThatUsesSuper})|.
Allen
es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss
Thanks, Sean Eagan
From: Sean Eagan <seaneagan1 at gmail.com> Date: June 22, 2011 15:48:18 GMT+02:00
There is no infinite recursion hazard in the semantics that Axel suggested, and that I used in the gist I posted (gist.github.com/1036200), where |super| is essentially the "the [[Prototype]] internal property of the object on which the method or accessor getter or setter was found"
That would be the hypothetical "here".
except that the |this| used in method calls and accessor getters and setters accessed from |super|, e.g. super.method() or super.accessorWithGetterOrSetterThatUseSuper, is the |this| of the caller, rather than the |super| of the caller.
I don't think we need any "safety check" when assigning a method or accessor getter or setter that uses super to an object. The concept of "super" seems to be a relative one, it is the object one step up in the prototype chain from the current object. If a method or accessor getter or setter needs an absolute reference to the prototype of a given object, it should refer to it by name.
The problem is that JS currently does not have the notion of the “current object”, it only briefly appears when looking for a property in the prototype chain.
"here" = object where a property was found is in general not the same as "this": "this" always points to the beginning of the prototype chain, whereas "here" can point to any of its members. E.g. when you invoke var s = new String("abc"); s.trim(); then "here" === String.prototype, but "this" === s. Otherwise String.prototype.trim() could not access the properties of s (such as s.length).
On Jun 22, 2011, at 6:48 AM, Sean Eagan wrote:
There is no infinite recursion hazard in the semantics that Axel suggested, ...
Yes, I acknowledged that, but went on to repeat that an extra implicit parameter is too costly (as opposed to a new internal property of functions).
I don't think we need any "safety check" when assigning a method or accessor getter or setter that uses super to an object. The concept of "super" seems to be a relative one,
That is what we are arguing about. It's not a conclusion we all share.
it is the object one step up in the prototype chain from the current object.
The "current object" -- Axel's |here| -- is not exposed in JS. Exposing it requires another implicit parameter, which costs too much.
If a method or accessor getter or setter needs an absolute reference to the prototype of a given object, it should refer to it by name.
Agreed, especially when mutating. Bu the relativity of |super| is still in dispute (relative to what? when?).
Also, AFAICT there is no performance cost of a dynamic super beyond the performance cost of static super.
No, see above.
If there would be any difference between the two, it would be in the work that needs to be done to determine what the |super| binding of a particular call site should be. In either the dynamic or static case, the engine will climb the prototype chain when looking for a method or accessor property. When it finds one that matches, in the dynamic case the |super| binding will just be the [[Prototype]] internal property of the current prototype chain object,
And that must be passed as an extra implicit parameter, as is done with |this|.
in the static case it will be the [[???]] internal property of the method or accessor getter or setter found.
The [[Super]] property, let's say. Note how this is static indeed, set at function creation time. It is not set on each call. It is not a parameter passed by mutating the function on each call.
Thus, in both cases it will just be a matter of accessing an internal property of an object that is immediately accessible when climbing the prototype chain.
Here you seem to assume the |here| result found during callee-expression evaluation in your proposal can be stored in [[Super]]. That's still an implicit per-call parameter (an even less efficient way to pass it, by side-effecting the called function object, but never mind!). The problem is the per-call overhead.
On Wed, Jun 22, 2011 at 9:23 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
From: Sean Eagan <seaneagan1 at gmail.com> Date: June 22, 2011 15:48:18 GMT+02:00
There is no infinite recursion hazard in the semantics that Axel suggested, and that I used in the gist I posted (gist.github.com/1036200), where |super| is essentially the "the [[Prototype]] internal property of the object on which the method or accessor getter or setter was found"
That would be the hypothetical "here".
except that the |this| used in method calls and accessor getters and setters accessed from |super|, e.g. super.method() or super.accessorWithGetterOrSetterThatUseSuper, is the |this| of the caller, rather than the |super| of the caller.
I don't think we need any "safety check" when assigning a method or accessor getter or setter that uses super to an object. The concept of "super" seems to be a relative one, it is the object one step up in the prototype chain from the current object. If a method or accessor getter or setter needs an absolute reference to the prototype of a given object, it should refer to it by name.
The problem is that JS currently does not have the notion of the “current object”, it only briefly appears when looking for a property in the prototype chain.
ES does have a concept of "current object" or "object on which the property was found", it's just implicit in the current spec. In ES5, prototype climbing occurs in 8.12.2 [[GetProperty]] (P) step 5. The "object on which the property was found" is just the "object on which [[GetProperty]] found the property in step 1 (where it calls [[GetOwnProperty]]). I imagine that in reality [[GetProperty]] is generally implemented iteratively rather than recursively for performance reasons, in which case implementations would not need to actually return the object up the "native call stack" or anything.
"here" = object where a property was found is in general not the same as "this": "this" always points to the beginning of the prototype chain, whereas "here" can point to any of its members. E.g. when you invoke var s = new String("abc"); s.trim(); then "here" === String.prototype, but "this" === s. Otherwise String.prototype.trim() could not access the properties of s (such as s.length).
Right, |super| would be the [[Prototype]] internal property of the |here| concept that you introduced. As Brendan pointed out though, |here| breaks abstractions, and is not a reserved word, so it should not actually be exposed.
On Wed, Jun 22, 2011 at 10:01 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 22, 2011, at 6:48 AM, Sean Eagan wrote:
I don't think we need any "safety check" when assigning a method or accessor getter or setter that uses super to an object. The concept of "super" seems to be a relative one,
That is what we are arguing about. It's not a conclusion we all share.
In ES, functions are first class objects not owned by any one object, an "absolute" super breaks this.
it is the object one step up in the prototype chain from the current object.
The "current object" -- Axel's |here| -- is not exposed in JS. Exposing it requires another implicit parameter, which costs too much.
I'm not suggesting that |here| be exposed just |super|.
The |super| I am suggesting is not an "additional implicit parameter", it's just a value that is determined while walking the prototype chain of the existing |this| implicit parameter. Why would this value be more expensive to access when resolving |super| in the function activation than the [[Super]] internal property of the function would be?
If a method or accessor getter or setter needs an absolute reference to the prototype of a given object, it should refer to it by name.
Agreed, especially when mutating. Bu the relativity of |super| is still in dispute (relative to what? when?).
what: object on which the property was found when: during prototype climbing
Thus, in both cases it will just be a matter of accessing an internal property of an object that is immediately accessible when climbing the prototype chain.
Here you seem to assume the |here| result found during callee-expression evaluation in your proposal can be stored in [[Super]]. That's still an implicit per-call parameter (an even less efficient way to pass it, by side-effecting the called function object, but never mind!). The problem is the per-call overhead.
No, I'm not suggesting to store the |super| ( |here|.[[Prototype]] ) result permanently anywhere, just to keep it accessible during the function activation. The value has already been calculated via the prototype climbing, what is the overhead in keeping it around?
On Jun 22, 2011, at 8:28 AM, Sean Eagan wrote:
On Wed, Jun 22, 2011 at 10:01 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 22, 2011, at 6:48 AM, Sean Eagan wrote:
I don't think we need any "safety check" when assigning a method or accessor getter or setter that uses super to an object. The concept of "super" seems to be a relative one,
That is what we are arguing about. It's not a conclusion we all share.
In ES, functions are first class objects not owned by any one object, an "absolute" super breaks this.
Functions have static scope (see the [[Scope]] internal property). This is absolute and with very good reason!
The "current object" -- Axel's |here| -- is not exposed in JS. Exposing it requires another implicit parameter, which costs too much.
I'm not suggesting that |here| be exposed just |super|.
Your proposal requires another (implicit) parameter to functions taking super.
Since we do not know at the call site whether a function takes super, it requires an extra parameter for every call. This costs too much.
On Wed, Jun 22, 2011 at 10:35 AM, Brendan Eich <brendan at mozilla.com> wrote:
Functions have static scope (see the [[Scope]] internal property). This is absolute and with very good reason!
Yes, functions have static scope, but functions (and all objects) do not have static owning objects, they are free to be passed around and assigned as desired, this is what a static super breaks.
Your proposal requires another (implicit) parameter to functions taking super.
Since we do not know at the call site whether a function takes super, it requires an extra parameter for every call. This costs too much.
Its value is already resolved via prototype climbing, I don't see the tremendous cost is simply making this value accessible within the function activation which occurs as a result of the prototype climbing.
On Wed, Jun 22, 2011 at 1:01 PM, Sean Eagan <seaneagan1 at gmail.com> wrote:
Its value is already resolved via prototype climbing, I don't see the tremendous cost is simply making this value accessible within the function activation which occurs as a result of the prototype climbing.
Can the value of a dynamic super be unambiguously resolved with prototype climbing and without an extra implicit parameter?
Juan
On Wed, Jun 22, 2011 at 8:28 AM, Sean Eagan <seaneagan1 at gmail.com> wrote:
On Wed, Jun 22, 2011 at 10:01 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 22, 2011, at 6:48 AM, Sean Eagan wrote:
I don't think we need any "safety check" when assigning a method or accessor getter or setter that uses super to an object. The concept of "super" seems to be a relative one,
That is what we are arguing about. It's not a conclusion we all share.
In ES, functions are first class objects not owned by any one object, an "absolute" super breaks this.
I agree that dynamic "this" but static "super" seems odd but maybe symmetry with "this" is not desired. Maybe we should have static "super" and be looking for symmetry with a static "self".
Peter
On Wed, Jun 22, 2011 at 12:33 PM, Peter Michaux <petermichaux at gmail.com> wrote:
I agree that dynamic "this" but static "super" seems odd but maybe symmetry with "this" is not desired. Maybe we should have static "super" and be looking for symmetry with a static "self".
Symmetry with |this| is important because |super| uses it's sibling |this| as the |this| in methods and accessor getter and setter calls that result from |super| property access.
If by "self" you mean the same thing as Axel's "here", then I don't think that is desireable as Brendan earlier pointed out.
On Wed, Jun 22, 2011 at 12:07 PM, Juan Ignacio Dopazo <dopazo.juan at gmail.com> wrote:
Can the value of a dynamic super be unambiguously resolved with prototype climbing and without an extra implicit parameter?
Yes, it can be unambiguously determined by prototype climbing, the only information from the call site that is used is the base ( |this| ) value of the method call or accessor property access, whose prototype chain is the one that is climbed.
On Jun 22, 2011, at 9:01 AM, Sean Eagan wrote:
On Wed, Jun 22, 2011 at 10:35 AM, Brendan Eich <brendan at mozilla.com> wrote:
Functions have static scope (see the [[Scope]] internal property). This is absolute and with very good reason!
Yes, functions have static scope, but functions (and all objects) do not have static owning objects,
The scope in ES1-5 can be an object, the global object. Your use of "owning" here either covers that case, in which your claim is false, or you need to define it better.
Many functions are used only as methods via prototypal inheritance, stored in one object (the prototype). Capturing that object's [[Prototype]] unambiguously via 'super' is useful. If it doesn't cover the dynamic case, use Object.defineMethod.
they are free to be passed around and assigned as desired, this is what a static super breaks.
"breaks" is too strong. Object.defineMethod as a hard-case solution is being discussed. It's not the end of the world.
Your proposal requires another (implicit) parameter to functions taking super.
Since we do not know at the call site whether a function takes super, it requires an extra parameter for every call. This costs too much.
Its value is already resolved via prototype climbing,
Please, you are rehashing. We all agree on this. What is in dispute is the cost of passing this object as an extra implicit parameter.
I don't see the tremendous cost is simply making this value accessible within the function activation which occurs as a result of the prototype climbing.
Don't bring up arguments again. That object is optimized away in current engines for important cases, but it's a pain in the ass due to the required alias and escape analyses. We do not want more like that for 'super'.
The cost is naively an extra push or register, which is big enough that I can promise you: engine implementors will reject this. You are barking up the wrong tree here.
Want to clarify one thing: 'super' in the classes proposal has well-defined static meaning. I don't think you are trying to change that -- tell me if I'm wrong. 'super' in functions or methods in an object literal is what you're talking about.
On Jun 22, 2011, at 11:01 AM, Sean Eagan wrote:
On Wed, Jun 22, 2011 at 12:07 PM, Juan Ignacio Dopazo <dopazo.juan at gmail.com> wrote:
Can the value of a dynamic super be unambiguously resolved with prototype climbing and without an extra implicit parameter?
Yes, it can be unambiguously determined by prototype climbing, the only information from the call site that is used is the base ( |this| ) value of the method call or accessor property access, whose prototype chain is the one that is climbed.
I hope you aren't proposing lazy re-climbing on the first or all evaluations of 'super'. That will not work. The actual 'here' or "method home" found during callee evaluation must be used. You cannot assume prototype lookup is idempotent.
On Wed, Jun 22, 2011 at 3:01 PM, Sean Eagan <seaneagan1 at gmail.com> wrote:
On Wed, Jun 22, 2011 at 12:07 PM, Juan Ignacio Dopazo <dopazo.juan at gmail.com> wrote:
Can the value of a dynamic super be unambiguously resolved with prototype climbing and without an extra implicit parameter?
Yes, it can be unambiguously determined by prototype climbing, the only information from the call site that is used is the base ( |this| ) value of the method call or accessor property access, whose prototype chain is the one that is climbed.
Cheers, Sean Eagan
If I understood correctly this thread, the problem seems to be in distinguishing between |this| and the prototype that "owns" the function. Let me see if I can explain it...
var A = { method: function () { /* do something */ } };
var B = Object.create(A); B.method = function () { Object.getPrototypeOf(this).method(); // if you do this, then in the context of A.method |this| will be A, not B Object.getPrototypeOf(this).method.call(this); // if you do this, then it's all ok unless you want add another level };
var C = Object.create(B); C.method = function () { Object.getPrototypeOf(this).method.call(this); // Now in B.method |this| will be C and getPrototypeOf will be used with C... an infinite loop };
IIRC, this is why a |here| value is needed.
Juan
On Wed, Jun 22, 2011 at 2:10 PM, Brendan Eich <brendan at mozilla.com> wrote:
Yes, functions have static scope, but functions (and all objects) do not have static owning objects, The scope in ES1-5 can be an object, the global object. Your use of "owning" here either covers that case, in which your claim is false, or you need to define it better.
By "owning object" I mean an object which has the function (or arbitrary object) as one (or more) of its properties. Regardless of a function's scope, it may have multiple owning objects, one of which may be the global object, as opposed to a single permanent (static) owning object.
Many functions are used only as methods via prototypal inheritance, stored in one object (the prototype). Capturing that object's [[Prototype]] unambiguously via 'super' is useful. If it doesn't cover the dynamic case, use Object.defineMethod.
they are free to be passed around and assigned as desired, this is what a static super breaks. "breaks" is too strong. Object.defineMethod as a hard-case solution is being discussed. It's not the end of the world.
I don't think of assigning functions dynamically and to multiple objects is a "hard case" I think it's pretty common. Requiring Object.defineMethod for these cases is inconvenient, and the assigner may not even know if the function uses 'super'. Also, see my previous comments to Allen regarding Object.defineMethod.
The cost is naively an extra push or register, which is big enough that I can promise you: engine implementors will reject this.
If this is true then dynamic super will not work, I am not knowledgeable enough about implementations to argue here.
Is there no per-call cost whatsoever to adding static super?
Want to clarify one thing: 'super' in the classes proposal has well-defined static meaning. I don't think you are trying to change that -- tell me if I'm wrong. 'super' in functions or methods in an object literal is what you're talking about.
I'm talking about |super| working in any function and having property access work with it as I think Allen is as well with the combination of object literal super and Object.defineMethod.
Sorry for any earlier rehashing, I didn't realize we agreed on those points.
Thanks, Sean Eagan
On Wed, Jun 22, 2011 at 2:11 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 22, 2011, at 11:01 AM, Sean Eagan wrote:
On Wed, Jun 22, 2011 at 12:07 PM, Juan Ignacio Dopazo <dopazo.juan at gmail.com> wrote:
Can the value of a dynamic super be unambiguously resolved with prototype climbing and without an extra implicit parameter?
Yes, it can be unambiguously determined by prototype climbing, the only information from the call site that is used is the base ( |this| ) value of the method call or accessor property access, whose prototype chain is the one that is climbed.
I hope you aren't proposing lazy re-climbing on the first or all evaluations of 'super'. That will not work. The actual 'here' or "method home" found during callee evaluation must be used. You cannot assume prototype lookup is idempotent.
Correct, no new prototype climbing that doesn't already occur.
On Jun 22, 2011, at 2:36 PM, Sean Eagan wrote:
On Wed, Jun 22, 2011 at 2:10 PM, Brendan Eich <brendan at mozilla.com> wrote:
Yes, functions have static scope, but functions (and all objects) do not have static owning objects, The scope in ES1-5 can be an object, the global object. Your use of "owning" here either covers that case, in which your claim is false, or you need to define it better.
By "owning object" I mean an object which has the function (or arbitrary object) as one (or more) of its properties. Regardless of a function's scope, it may have multiple owning objects, one of which may be the global object, as opposed to a single permanent (static) owning object.
This is all theoretical. In my experience, methods written either as function-valued properties in object literals, or goog.foo.bar = function (){} assignments, are the norm and there is only one "here" for each such method.
Is there no per-call cost whatsoever to adding static super?
No, it's static -- an internal property of the function object set once on creation and (in my view) never changed thereafter unless unobservably (Allen's point about optimizing Object.defineMethod when "handing off" an otherwise-useless function expression).
On Jun 22, 2011, at 2:37 PM, Sean Eagan wrote:
On Wed, Jun 22, 2011 at 2:11 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 22, 2011, at 11:01 AM, Sean Eagan wrote:
On Wed, Jun 22, 2011 at 12:07 PM, Juan Ignacio Dopazo <dopazo.juan at gmail.com> wrote:
Can the value of a dynamic super be unambiguously resolved with prototype climbing and without an extra implicit parameter?
Yes, it can be unambiguously determined by prototype climbing, the only information from the call site that is used is the base ( |this| ) value of the method call or accessor property access, whose prototype chain is the one that is climbed.
I hope you aren't proposing lazy re-climbing on the first or all evaluations of 'super'. That will not work. The actual 'here' or "method home" found during callee evaluation must be used. You cannot assume prototype lookup is idempotent.
Correct, no new prototype climbing that doesn't already occur.
Then that means an extra implicit parameter beyond |this| for every call site.
From: Brendan Eich <brendan at mozilla.com> Date: June 23, 2011 0:21:17 GMT+02:00
Is there no per-call cost whatsoever to adding static super?
No, it's static -- an internal property of the function object set once on creation and (in my view) never changed thereafter unless unobservably (Allen's point about optimizing Object.defineMethod when "handing off" an otherwise-useless function expression).
How does a function get access to its properties? Doesn’t the ability to access "thisFunction" incur any costs? I would expect static super to work as follows:
var Sub = Super <| { foo: function me(x) { me.super.foo.call(this, x); } }
Does the named function expression cost nothing here?
On Jun 22, 2011, at 4:02 PM, Axel Rauschmayer wrote:
From: Brendan Eich <brendan at mozilla.com> Date: June 23, 2011 0:21:17 GMT+02:00
Is there no per-call cost whatsoever to adding static super?
No, it's static -- an internal property of the function object set once on creation and (in my view) never changed thereafter unless unobservably (Allen's point about optimizing Object.defineMethod when "handing off" an otherwise-useless function expression).
How does a function get access to its properties? Doesn’t the ability to access "thisFunction" incur any costs? I would expect static super to work as follows:
var Sub = Super <| { foo: function me(x) { me.super.foo.call(this, x); } }
Does the named function expression cost nothing here?
This is entirely beside the point.
Dynamic |super| as Sean proposes requires every call site to pass the |here| parameter or something derived from it, no way around that.
Paying for 'super' if you use it, buying by the yard, is not a problem.
Making every function call in the language slow, increasing register pressure, etc. -- absent aggressive inference to identify callees and specialize call sites to them (inlining VMs do this but it can backfire, so there will be a default case that can't do this) -- is a big problem.
On Jun 23, 2011, at 7:04 AM, Brendan Eich wrote:
On Jun 22, 2011, at 4:02 PM, Axel Rauschmayer wrote:
Does the named function expression cost nothing here?
This is entirely beside the point.
Dynamic |super| as Sean proposes requires every call site to pass the |here| parameter or something derived from it, no way around that.
Paying for 'super' if you use it, buying by the yard, is not a problem.
Making every function call in the language slow, increasing register pressure, etc. -- absent aggressive inference to identify callees and specialize call sites to them (inlining VMs do this but it can backfire, so there will be a default case that can't do this) -- is a big problem.
/be
If you don't have actual implementation experience with dynamic languages I think you guys will just have to take Brendan's and my word for this. Dynamic super would have unacceptable overhead on every function call.
Dynamic super may be conceptually slightly cleaner conceptually in that it eliminates the need for Object.defineMethod. However, the cost is unacceptably high.
Pragmatically, the choice is between static super and no super. Insisting on dynamic super just pushes the discussion in the no super direction.
This is entirely beside the point.
Dynamic |super| as Sean proposes requires every call site to pass the |here| parameter or something derived from it, no way around that.
Paying for 'super' if you use it, buying by the yard, is not a problem.
Making every function call in the language slow, increasing register pressure, etc. -- absent aggressive inference to identify callees and specialize call sites to them (inlining VMs do this but it can backfire, so there will be a default case that can't do this) -- is a big problem.
I believe you about the dynamic super. I can summarize my question as follows:
- Making "super" or "current object" available to a function incurs costs.
- Making "current function" available to a function does not incur costs? This is not an extra parameter, then?
On Jun 23, 2011, at 12:31 PM, Axel Rauschmayer wrote:
This is entirely beside the point.
Dynamic |super| as Sean proposes requires every call site to pass the |here| parameter or something derived from it, no way around that.
Paying for 'super' if you use it, buying by the yard, is not a problem.
Making every function call in the language slow, increasing register pressure, etc. -- absent aggressive inference to identify callees and specialize call sites to them (inlining VMs do this but it can backfire, so there will be a default case that can't do this) -- is a big problem.
I believe you about the dynamic super. I can summarize my question as follows:
- Making "super" or "current object" available to a function incurs costs.
If it is statically bound (via a [[Super]] or [[Current]] internal property then there is only a cost (and a small one) on actual accesses of super. EG, Object.defineMethod(obj,'foo", function() {return super.foo() /*cost to access [[Super]] + cost to access this */});
If dynamically bound (on each property lookup of the method) then there is a cost on every function call to pass the current object reference to the function invocation. This cost is in addition to the const of passing thethis reference.
- Making "current function" available to a function does not incur costs? This is not an extra parameter, then?
the current function is made available via the function name in a function declaration: var q= -1; Object.defineMethod(obj"foo",function bar(a,b,c) {return qbar.length / returns -3 */});
bar is a lexically scoped binding just like q. No per call perimeters are involved. The cost to access bar is approximately the same as the cost of accessing q (exact costs subject to details of how lexically scoped access is implemented)
- Making "current function" available to a function does not incur costs? This is not an extra parameter, then?
the current function is made available via the function name in a function declaration: var q= -1; Object.defineMethod(obj"foo",function bar(a,b,c) {return qbar.length / returns -3 */});
bar is a lexically scoped binding just like q. No per call perimeters are involved. The cost to access bar is approximately the same as the cost of accessing q (exact costs subject to details of how lexically scoped access is implemented)
Isn’t "bar" a named function expression and won't this binding be local to the expression (unlike q)? But I don’t think that changes the cost.
On Thu, Jun 23, 2011 at 4:56 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
If you don't have actual implementation experience with dynamic languages I think you guys will just have to take Brendan's and my word for this.
I now understand and fully agree with your performance arguments against dynamic super. However, I still believe that the dynamic super semantics are largely superior to the static super semantics.
Fortunately, I think the dynamic super semantics can be simulated via static super, thus yielding the best of both approaches!
Here's what I propose:
Terminology:
super function - a function that uses |super| super delegate function - A function which delegates to a super function stored in its [[SuperFunction]] internal property, passing a |super| binding stored in its [[Super]] internal property.
Semantics:
When a super function is assigned as a method or accessor getter or setter either via an object literal, Object.defineProperty or direct assignment, a super delegate function is created and assigned instead whose [[SuperFunction]] internal property is the super function being assigned, and whose [[Super]] internal property is the [[Prototype]] internal property of the object being assigned to.
When a super delegate function is subsequently accessed from an object, e.g. o.methodThatUsesSuper or Object.getOwnPropertyDescriptor(o, "accessorWithGetterOrSetterThatUsesSuper"), this resolves to the [[TargetFunction]] internal property of the super delegate function.
When a super function is called directly, e.g. superFunction() or superFunction.call(this), |super| is resolved as the [[Prototype]] internal property of the |this| of the call.
When a super delegate function is called, e.g. o.methodThatUsesSuper(), o.accessorWithGetterThatUsesSuper, or o.accessorWithSetterThatUsesSuper = "foo", |super| is bound to the super delegate function's [[Super]] internal property.
The equivalence class of a super function in == and === consists of the super function and all of its corresponding super delegate functions, to illustrate:
let f = function(){return super.x}, o1 = {method: f}, o2 = Object.create(null, {method: {get: f}}); assert(f === o1.method); // true assert(o1.method === Object.getOwnPropertyDescriptor(o2, "method").get); // true
Thus, the static super implementation is completely abstracted from the user, and they benefit from the full dynamic super semantics!
I'm sure there are still details to work out, but it seems quite promising to me.
On Jun 23, 2011, at 2:56 PM, Axel Rauschmayer wrote:
- Making "current function" available to a function does not incur costs? This is not an extra parameter, then?
the current function is made available via the function name in a function declaration: var q= -1; Object.defineMethod(obj"foo",function bar(a,b,c) {return qbar.length / returns -3 */});
bar is a lexically scoped binding just like q. No per call perimeters are involved. The cost to access bar is approximately the same as the cost of accessing q (exact costs subject to details of how lexically scoped access is implemented)
Isn’t "bar" a named function expression and won't this binding be local to the expression (unlike q)? But I don’t think that changes the cost.
Yes, that was the reason for my final comment about the exact cost. Depending upon the implementation details there may be minor difference in the cost of accessing "bar" and "q". However, any such difference is likely to be small and of little significance.
Technically, according to the language specification, "q", "bar", and the function arguments "a","b","c" are in different scope contours.
A couple corrections below:
On Thu, Jun 23, 2011 at 9:14 AM, Sean Eagan <seaneagan1 at gmail.com> wrote:
When a super delegate function is subsequently accessed from an object, e.g. o.methodThatUsesSuper or Object.getOwnPropertyDescriptor(o, "accessorWithGetterOrSetterThatUsesSuper"), this resolves to the [[TargetFunction]] internal property of the super delegate function.
[[TargetFunction]] should be [[SuperFunction]]
The equivalence class of a super function in == and === consists of the super function and all of its corresponding super delegate functions
Actually, it's simpler than this. One can never obtain a reference to a super delegate function, only call it as a method or accessor getter or setter.
On Jun 23, 2011, at 3:14 PM, Sean Eagan wrote:
On Thu, Jun 23, 2011 at 4:56 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
If you don't have actual implementation experience with dynamic languages I think you guys will just have to take Brendan's and my word for this.
I now understand and fully agree with your performance arguments against dynamic super. However, I still believe that the dynamic super semantics are largely superior to the static super semantics.
Fortunately, I think the dynamic super semantics can be simulated via static super, thus yielding the best of both approaches!
Here's what I propose:
Terminology:
super function - a function that uses |super| super delegate function - A function which delegates to a super function stored in its [[SuperFunction]] internal property, passing a |super| binding stored in its [[Super]] internal property.
so every super function still requires an implicit super parameter
Semantics:
When a super function is assigned as a method or accessor getter or setter either via an object literal, Object.defineProperty or direct assignment,
this would add overhead to every [[Put]]. a test to see if the value is a super function. The way to avoid the test would be defer the test until the value of the property was retrieved (first time only perhaps. This is essentially the same as the dynamic super implementation strategy of conditionally only passing the current object to a super function. So, you have traded overhead on ever call for over head on every [[Put]]. [[Put]]'s probaby occur less often than [[Call]]'s but may still be too much.
a super delegate function is created and assigned instead whose [[SuperFunction]] internal property is the super function being assigned, and whose [[Super]] internal property is the [[Prototype]] internal property of the object being assigned to.
When a super delegate function is subsequently accessed from an object, e.g. o.methodThatUsesSuper or Object.getOwnPropertyDescriptor(o, "accessorWithGetterOrSetterThatUsesSuper"), this resolves to the [[TargetFunction]] internal property of the super delegate function.
I believe you are saying that upon property revival, a super delegate function reference is replaced with the value of its [[SuperFunction]]. This means there is an additional check (is the property value a super delegate function) on every [[Get]].
When a super function is called directly, e.g. superFunction() or superFunction.call(this), |super| is resolved as the [[Prototype]] internal property of the |this| of the call.
How does it know it is being called directly?
This seems to be the primary thing this approach adds to simple static super. Why is this really important? Is there a common usage case that requires this. I don't believe it has been addressed by other OO languages.
When a super delegate function is called, e.g. o.methodThatUsesSuper(), o.accessorWithGetterThatUsesSuper, or o.accessorWithSetterThatUsesSuper = "foo", |super| is bound to the super delegate function's [[Super]] internal property.
in other words, the super delegate function calls the super function passing the value of its [[Super]] as the implicit super parameter
The equivalence class of a super function in == and === consists of the super function and all of its corresponding super delegate functions, to illustrate:
let f = function(){return super.x}, o1 = {method: f}, o2 = Object.create(null, {method: {get: f}}); assert(f === o1.method); // true assert(o1.method === Object.getOwnPropertyDescriptor(o2, "method").get); // true
Thus, the static super implementation is completely abstracted from the user, and they benefit from the full dynamic super semantics!
At the expense of additional runtime overhead in common runtime semantic primitives. Why is this equivalence important? Why isn't it reasonable that two function objects derived from the same declaration but with different super bindings will compare as not ===. This doesn't seem any more unusual then the fact that a single function declaration with no free references still creates different function objects each time it is evaluated.
On Jun 23, 2011, at 7:43 AM, Sean Eagan wrote:
Actually, it's simpler than this. One can never obtain a reference to a super delegate function, only call it as a method or accessor getter or setter.
JS has first-class functions. We are not going to make invoke-only methods. You're way up that wrong tree.
On Jun 23, 2011, at 4:31 AM, Axel Rauschmayer wrote:
This is entirely beside the point.
Dynamic |super| as Sean proposes requires every call site to pass the |here| parameter or something derived from it, no way around that.
Paying for 'super' if you use it, buying by the yard, is not a problem.
Making every function call in the language slow, increasing register pressure, etc. -- absent aggressive inference to identify callees and specialize call sites to them (inlining VMs do this but it can backfire, so there will be a default case that can't do this) -- is a big problem.
I believe you about the dynamic super. I can summarize my question as follows:
- Making "super" or "current object" available to a function incurs costs.
These are two separate costs. You didn't define which 'super' but from context I'll assume static. That's a function-creation-time internal property setting cost.
If by "current object" you mean |here|, please use that term for clarity's sake. That has a per-call cost: an extra implicit parameter. We've been over this about five times.
- Making "current function" available to a function does not incur costs? This is not an extra parameter, then?
Do you mean "current object", aka |here|?
If so, that's an extra parameter.
If not, I don't know what you mean.
I believe you about the dynamic super. I can summarize my question as follows:
- Making "super" or "current object" available to a function incurs costs.
These are two separate costs. You didn't define which 'super' but from context I'll assume static. That's a function-creation-time internal property setting cost.
If by "current object" you mean |here|, please use that term for clarity's sake. That has a per-call cost: an extra implicit parameter. We've been over this about five times.
Yes, I’m not questioning, just repeating and agreeing. And comparing the cost of |here| to the cost |thisFunction|.
- Making "current function" available to a function does not incur costs? This is not an extra parameter, then?
Do you mean "current object", aka |here|?
If so, that's an extra parameter.
If not, I don't know what you mean.
Static super can only be accessed if you have a reference |thisFunction| to the function that is currently executed. I would think that |thisFunction| would also incur costs, but Allen has since said that these costs are negligible.
Related topic: How would |super| be handled if |this| is lexical?
Ok, sorry it took me so long, but I agree that the general idea of Object.defineMethod is the best solution. I think it can be improved upon though, by adding the following:
Ability to use any property descriptor attributes when defining methods. Ability to assign accessor getters and setters as well which use the assigned to object as |super|. Avoid having to add a "defineMethod" proxy trap.
Here are the two potential solutions I have in mind for this:
-
Have Object.defineProperty create new functions with updated [[Super]] when the "value", "get", or "set" attributes are functions that use super.
-
Add an Object.defineSuperProperty which is the same as the existing Object.defineProperty except that it creates new functions with updated [[Super]] when the "value", "get", or "set" attributes are functions that use super. It could potentially throw if no super functions are passed. This one may still require a "defineSuperProperty" proxy trap, not sure.
Also, I wonder if there is a need for a Function.prototype.isSuper similar to Function.prototype.isGenerator?
On Jun 23, 2011, at 12:07 PM, Axel Rauschmayer wrote:
I believe you about the dynamic super. I can summarize my question as follows:
- Making "super" or "current object" available to a function incurs costs.
These are two separate costs. You didn't define which 'super' but from context I'll assume static. That's a function-creation-time internal property setting cost.
If by "current object" you mean |here|, please use that term for clarity's sake. That has a per-call cost: an extra implicit parameter. We've been over this about five times.
Yes, I’m not questioning, just repeating and agreeing. And comparing the cost of |here| to the cost |thisFunction|.
Oh, you meant the callee (the called function)? The "current object" phrase threw me.
- Making "current function" available to a function does not incur costs? This is not an extra parameter, then?
Do you mean "current object", aka |here|?
If so, that's an extra parameter.
If not, I don't know what you mean.
Static super can only be accessed if you have a reference |thisFunction| to the function that is currently executed. I would think that |thisFunction| would also incur costs, but Allen has since said that these costs are negligible.
The called function is part of the evaluation results needed to call the function. It is indeed passed on the stack in naive stack-based implementations, to support reflection (e.g. arguments usage by the callee; again remember the caller in general does not know whether the callee uses arguments, debugger, etc.), and debugging (kind of hyper-reflection).
In a JIT, the calling convention can use registers and avoid writing to memory more than the minimum needed based on whatever analysis the JIT can support.
In any event, there's no extra parameter beyond the situation in today's engines. With dynamic 'super', there is.
Related topic: How would |super| be handled if |this| is lexical?
There's no connection, AFAIK. But we also don't have a lexical-this proposal in Harmony yet.
On Jun 23, 2011, at 2:00 PM, Sean Eagan wrote:
Ok, sorry it took me so long, but I agree that the general idea of Object.defineMethod is the best solution. I think it can be improved upon though, by adding the following:
Ability to use any property descriptor attributes when defining methods. Ability to assign accessor getters and setters as well which use the assigned to object as |super|. Avoid having to add a "defineMethod" proxy trap.
Great point! That would be something to avoid.
Here are the two potential solutions I have in mind for this:
- Have Object.defineProperty create new functions with updated [[Super]] when the "value", "get", or "set" attributes are functions that use super.
This seems enough, you're right.
- Add an Object.defineSuperProperty which is the same as the existing Object.defineProperty except that it creates new functions with updated [[Super]] when the "value", "get", or "set" attributes are functions that use super. It could potentially throw if no super functions are passed. This one may still require a "defineSuperProperty" proxy trap, not sure.
I'm not sure we need this one. Does it really avoid the proxy trap addition?
Also, I wonder if there is a need for a Function.prototype.isSuper similar to Function.prototype.isGenerator?
A generator is a kind of function. 'super' evaluates to an object. So this seems misplaced, but first, what use-case does it serve? Can you show an example that motivates it?
Related topic: How would |super| be handled if |this| is lexical?
There's no connection, AFAIK. But we also don't have a lexical-this proposal in Harmony yet.
I would expect problems that are similar to that = this:
var obj = { mymethod: function(arr) { arr.forEach(function(x) { super.mymethod(x); // would this work? }); } }
Thankfully, I don’t think that super-calls will be very common in typical JavaScript code, in contrast to "this".
On Jun 23, 2011, at 10:39 PM, Axel Rauschmayer wrote:
Related topic: How would |super| be handled if |this| is lexical?
There's no connection, AFAIK. But we also don't have a lexical-this proposal in Harmony yet.
I would expect problems that are similar to that = this:
var obj = { mymethod: function(arr) { arr.forEach(function(x) { super.mymethod(x); // would this work? }); } }
From harmony:object_initialiser_super :
PrimaryExpression : ... super The value of super is the same as the value of this but when super is used as the base of a property access the property lookup starts with the object that is the prototype of the object defined by the object literal that contains the reference to super.
This seems to say that the wrong |this| would be used, with the right property lookup starting point. Yikes.
Thankfully, I don’t think that super-calls will be very common in typical JavaScript code, in contrast to "this".
Not sure this will all pan out as proposed. 'super' in classes works. 'super' in any function nested however deeply in an object initialiser, perhaps not. If the above spec constrained the nesting to be immedate method of, that would seem to solve it. But would that be too restrictive?
On Jun 23, 2011, at 11:16 PM, Brendan Eich wrote:
On Jun 23, 2011, at 2:00 PM, Sean Eagan wrote:
Ok, sorry it took me so long, but I agree that the general idea of Object.defineMethod is the best solution. I think it can be improved upon though, by adding the following:
Ability to use any property descriptor attributes when defining methods. Ability to assign accessor getters and setters as well which use the assigned to object as |super|. Avoid having to add a "defineMethod" proxy trap.
Great point! That would be something to avoid.
I don't think that defineMethod implies the need for an additional proxy trap. defineMethod essentially first conditionally transforms its function parameter and then does a [[Put]] of that value. From a proxy perspective can simply be view as a function that does a [[Put]].
Here are the two potential solutions I have in mind for this:
- Have Object.defineProperty create new functions with updated [[Super]] when the "value", "get", or "set" attributes are functions that use super.
This seems enough, you're right.
I agree that the Object.defineProperty should apply defineMethod semantics when processing a "get" or "set" attributed as these are essentially method definition operations.
I don't think it should do so for "value". It is possible that a data property is being used as a function valued data store rather than as a method binding on an object. For example, a property might be used to store a callback function. Such a data-value function might reference |super|. For example, it might be a function with a bound |this| that also uses |super|. I don't thing we want the |super| binding changing in such situations. (Looking at this another way, if such a transformation of done in Object.defineProperty and not in [[DefineOwnProperty]] then there would be a semantic difference between obj.prop=func and Object.defineProperty(obj,"prop",{value:func}) that we probably don't want to have.)
I introduced the concept of defineMethod to define a means to explicitly disambiguate the attachment of a method and the setting of a data property value in situations where it makes a difference. People will run into bugs where they used = or defineProperty with the value attribute when they really need to do a defineMethod but at least with defineMethod there is a way to fix this bug.
- Add an Object.defineSuperProperty which is the same as the existing Object.defineProperty except that it creates new functions with updated [[Super]] when the "value", "get", or "set" attributes are functions that use super. It could potentially throw if no super functions are passed. This one may still require a "defineSuperProperty" proxy trap, not sure.
I'm not sure we need this one. Does it really avoid the proxy trap addition?
me neither
Also, I wonder if there is a need for a Function.prototype.isSuper similar to Function.prototype.isGenerator?
A generator is a kind of function. 'super' evaluates to an object. So this seems misplaced, but first, what use-case does it serve? Can you show an example that motivates it?
I think both of these fall into the realm of reflection. As such, I would want to keep them off the instances. If we need them I would make them: Function.isGenerator(func) Function.usesSuper(func)
however, WRT usesSuper, I isn't clear if it is anymore necessary than usesThis, hasNestedFunctions, hasIfStatements, and potentially hundreds of reflective queries that might be made about a function.
On Jun 24, 2011, at 6:47 AM, Brendan Eich wrote:
On Jun 23, 2011, at 10:39 PM, Axel Rauschmayer wrote:
Related topic: How would |super| be handled if |this| is lexical?
There's no connection, AFAIK. But we also don't have a lexical-this proposal in Harmony yet.
I would expect problems that are similar to that = this:
var obj = { mymethod: function(arr) { arr.forEach(function(x) { super.mymethod(x); // would this work? }); } }
From harmony:object_initialiser_super :
PrimaryExpression : ... super
The value of super is the same as the value of this but when super is used as the base of a property access the property lookup starts with the object that is the prototype of the object defined by the object literal that contains the reference to super.
This seems to say that the wrong |this| would be used, with the right property lookup starting point. Yikes.
Based upon these discussions, I would expect the inner function to have an unbound super as it is not declaratively a method and has not be bound as a method using Object.defineMethod. Note that in some earlier method I suggested that attempting to do a property lookup using an unbound this should throw.
The correct way to code the above would be:
var obj = { mymethod: function(arr) { let superMyMethod = super.myMethod; let self=this; arr.forEach(function(x) { superMyMethod.call.(self,x); }); } }
Which is a pretty good demo of both the value of a lexically scoped this and why a lexically scoped super should parallel it.
Thankfully, I don’t think that super-calls will be very common in typical JavaScript code, in contrast to "this".
Not sure this will all pan out as proposed. 'super' in classes works. 'super' in any function nested however deeply in an object initialiser, perhaps not. If the above spec constrained the nesting to be immedate method of, that would seem to solve it. But would that be too restrictive?
People will need to learn that super has the same lexical scoping hazards as this and that to use super in a function it must be bound as a method
On Fri, Jun 24, 2011 at 5:08 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
I agree that the Object.defineProperty should apply defineMethod semantics when processing a "get" or "set" attributed as these are essentially method definition operations. I don't think it should do so for "value".
I think it is inconsistent to update |super| for "get" and "set", but not for "value".
It is possible that a data property is being used as a function valued data store rather than as a method binding on an object. For example, a property might be used to store a callback function. Such a data-value function might reference |super|. For example, it might be a function with a bound |this| that also uses |super|. I don't thing we want the |super| binding changing in such situations. (Looking at this another way, if such a transformation of done in Object.defineProperty and not in [[DefineOwnProperty]] then there would be a semantic difference between obj.prop=func and Object.defineProperty(obj,"prop",{value:func}) that we probably don't want to have.)
I introduced the concept of defineMethod to define a means to explicitly disambiguate the attachment of a method and the setting of a data property value in situations where it makes a difference. People will run into bugs where they used = or defineProperty with the value attribute when they really need to do a defineMethod but at least with defineMethod there is a way to fix this bug.
I agree, this is why I proposed the Object.defineSuperProperty solution, as an explicit means to update |super|. It is like Object.defineMethod except that it allows you to use the "configurable", "writable", and "enumerable" attributes with methods, and is also an explicit means to update |super| for accessor getters and setters.
Object.defineSuperProperty also avoids a proxy trap because it can essentially have the same semantics as Object.defineProperty except that it creates functions with updated [[Super]] before passing the property descriptor to [[DefineOwnProperty]] where the "defineProperty" trap will be called.
Probably Object.defineSuperUsingProperty would be more accurate name.
Another option would be to add a boolean "updateSuper" argument to Object.defineProperty.
Another option would be an |Object.updateSuper(object, "foo")| which assigns functions with updated |super| to any "value", "get", and "set" attributes which contain functions which use |super| of an existing property, calling [[DefineOwnProperty]] with the new values.
Also, I wonder if there is a need for a Function.prototype.isSuper similar to Function.prototype.isGenerator?
A generator is a kind of function. 'super' evaluates to an object. So this seems misplaced, but first, what use-case does it serve? Can you show an example that motivates it?
I think both of these fall into the realm of reflection. As such, I would want to keep them off the instances. If we need them I would make them: Function.isGenerator(func)
I think Function.prototype.isGenerator appears to already be harmonized, so that should probably be discussed further then.
Function.usesSuper(func)
Yeah, that would probably be a better spot for it.
however, WRT usesSuper, I isn't clear if it is anymore necessary than usesThis, hasNestedFunctions, hasIfStatements, and potentially hundreds of reflective queries that might be made about a function.
It could be used to determine if you need to do an Object.defineSuperUsingProperty / Object.updateSuper / Object.defineMethod or whatever we land on.
On Jun 23, 2011, at 11:12 PM, Brendan Eich wrote:
On Jun 23, 2011, at 12:07 PM, Axel Rauschmayer wrote:
...
- Making "current function" available to a function does not incur costs? This is not an extra parameter, then?
Do you mean "current object", aka |here|?
If so, that's an extra parameter.
If not, I don't know what you mean.
Static super can only be accessed if you have a reference |thisFunction| to the function that is currently executed. I would think that |thisFunction| would also incur costs, but Allen has since said that these costs are negligible.
The called function is part of the evaluation results needed to call the function. It is indeed passed on the stack in naive stack-based implementations, to support reflection (e.g. arguments usage by the callee; again remember the caller in general does not know whether the callee uses arguments, debugger, etc.), and debugging (kind of hyper-reflection).
In a JIT, the calling convention can use registers and avoid writing to memory more than the minimum needed based on whatever analysis the JIT can support.
I'm my response I was referring to the lexically bound function name from a function expression as the means to access the "current function". That, presumably just uses the normal lexical addressing mechanism that is used to access any outer scope "variables".
In any event, there's no extra parameter beyond the situation in today's engines. With dynamic 'super', there is.
Related topic: How would |super| be handled if |this| is lexical?
There's no connection, AFAIK. But we also don't have a lexical-this proposal in Harmony yet.
If there was a mechanism for lexically addressing this, I would expect |super| to track |this| in parallel. From a value perspective, |super| is just a synonym for |this|.
On Jun 24, 2011, at 2:57 PM, Sean Eagan wrote:
On Fri, Jun 24, 2011 at 5:08 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
I agree that the Object.defineProperty should apply defineMethod semantics when processing a "get" or "set" attributed as these are essentially method definition operations. I don't think it should do so for "value".
I think it is inconsistent to update |super| for "get" and "set", but not for "value".
I contend that using defineProperty to to specify the get or set function of an accessor property is conceptually quite different from using defineProperty (or assignment) to modify the value of a data property. A put/get function is tightly linked to the object in the sense that it is automatically and transparently invoked as a method (with an implicitly supplied |this| value) and defineProperty is the only way to imperatively create such a method. As such defineProperty with a get/set attribute, to me, seems to clearly be a method definition (or perhaps better called method binding) operation.
The inconsistency is with defineProperty with the value attribute. That operation can be used for two conceptually different purposes. One purpose is simply to store a value for later retrieval, such a values include function objects. The other purpose is to associate a method with an object. In legacy ES, there was no real need to distinguish between these purposes. The introduction of |super| (and possibly other ES.next constructs such as private instance state) makes it important to distinguish these use cases. That is the reason for Object.defineMethod. For compatibility reasons, we can't change the semantics of defineProperty/value because it could break the storage/retrieval use case of properties. However, defineProperty/get/set is already a method binding operation. Giving it new extended semantics in the presence of a new language feature (super) would be a forward compatible change.
Legacy ES:
"method" definitions: defineProperty/data; defineProperty/get, defineProperty/put data property definitions: defineProperty/data
defineProperty/data is ambiguous -- it can be either a method or data property definition
Es.next:
"method" definitions: defineMethod; defineProperty/get, defineProperty/put data property definitions: defineProperty/data
Also ES programmers to avoid this ambiguity and permits associating new semantics with method definition that does not interfere with existing use of defineProperty/data
(more below)
It is possible that a data property is being used as a function valued data store rather than as a method binding on an object. For example, a property might be used to store a callback function. Such a data-value function might reference |super|. For example, it might be a function with a bound |this| that also uses |super|. I don't thing we want the |super| binding changing in such situations. (Looking at this another way, if such a transformation of done in Object.defineProperty and not in [[DefineOwnProperty]] then there would be a semantic difference between obj.prop=func and Object.defineProperty(obj,"prop",{value:func}) that we probably don't want to have.)
I introduced the concept of defineMethod to define a means to explicitly disambiguate the attachment of a method and the setting of a data property value in situations where it makes a difference. People will run into bugs where they used = or defineProperty with the value attribute when they really need to do a defineMethod but at least with defineMethod there is a way to fix this bug.
I agree, this is why I proposed the Object.defineSuperProperty solution, as an explicit means to update |super|. It is like Object.defineMethod except that it allows you to use the "configurable", "writable", and "enumerable" attributes with methods, and is also an explicit means to update |super| for accessor getters and setters.
Object.defineSuperProperty also avoids a proxy trap because it can essentially have the same semantics as Object.defineProperty except that it creates functions with updated [[Super]] before passing the property descriptor to [[DefineOwnProperty]] where the "defineProperty" trap will be called.
Probably Object.defineSuperUsingProperty would be more accurate name.
Another option would be to add a boolean "updateSuper" argument to Object.defineProperty.
Another option would be an |Object.updateSuper(object, "foo")| which assigns functions with updated |super| to any "value", "get", and "set" attributes which contain functions which use |super| of an existing property, calling [[DefineOwnProperty]] with the new values.
If we are defining a new definitional function, I want it to be able to do something more general then then just update the super binding. I also want it to be generalizable to deal with any future object/method binding semantics that introduced in ES.next or future revisions of the language. Object.updateMethod meets that criteria. Object.updateSuper does not.
...
Function.usesSuper(func)
Yeah, that would probably be a better spot for it.
however, WRT usesSuper, I isn't clear if it is anymore necessary than usesThis, hasNestedFunctions, hasIfStatements, and potentially hundreds of reflective queries that might be made about a function.
It could be used to determine if you need to do an Object.defineSuperUsingProperty / Object.updateSuper / Object.defineMethod or whatever we land on.
I don't want it to be necessary to make such determination. That breaks implementation encapsulation of the method. Someone installing a method should just say: Object.defineMethod(...) without having to worry about the implementation details of the method. Similarly, defining an accessor should simply be: Object.defineProperty(obj,name,{get:func1,set: func2)
On Jun 24, 2011, at 1:00 PM, Allen Wirfs-Brock wrote:
If there was a mechanism for lexically addressing this, I would expect |super| to track |this| in parallel. From a value perspective, |super| is just a synonym for |this|.
This is an important point, although what is a non-value perspective in JS? There are no explicit types. The answer must be an implicit type, the superclass view provided by the [[Prototype]] of the class prototype or ad-hoc containing object in which the method using 'super' was written.
Just a +1 here, and a request to make the proposal language support this as clearly (and up-front in the page) as possible.
On Jun 24, 2011, at 3:08 AM, Allen Wirfs-Brock wrote:
I don't think that defineMethod implies the need for an additional proxy trap. defineMethod essentially first conditionally transforms its function parameter and then does a [[Put]] of that value. From a proxy perspective can simply be view as a function that does a [[Put]].
I hope it's more than a [[Put]]. Normal [[Put]] does not clone its argument.
I introduced the concept of defineMethod to define a means to explicitly disambiguate the attachment of a method and the setting of a data property value in situations where it makes a difference. People will run into bugs where they used = or defineProperty with the value attribute when they really need to do a defineMethod but at least with defineMethod there is a way to fix this bug.
Can we do better? It seems this may be a "forgotten API", remembered only after painful debugging.
I think both of these fall into the realm of reflection. As such, I would want to keep them off the instances. If we need them I would make them: Function.isGenerator(func)
We started out there but ended up on Function.prototype. See
and the history at
doku.php?do=revisions&id=strawman%3Agenerators
leading to
See also
bugzilla.mozilla.org/show_bug.cgi?id=648355 (esp. comment 3) developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/isGenerator, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function#Methods_2
The impetus for putting reflection APIs on Object is mainly because Object.prototype is verboten. Not so with Function.prototype, and calling f.isGenerator() is much sweeter than calling Function.isGenerator(f). The latter must also handle f for all values, whereas the method works via prototypal delegation, so can be trapped by proxies, handled by ad-hoc wrappers, etc.
There is a serious issue when adding reflection APIs, of whether to support not only proxies via yet more traps (to be avoided), but also whether delegation via a method name found in an object or any value (possibly some we haven't thought of yet) is better than monkey-patching Function or Function.isGenerator.
I do not think we should take the Object-based reflection pattern as something to apply for all predicates that could be called reflective.
As an aside, Array.isArray is misplaced -- it should have gone on Object, to avoid the awkward and seemingly oxymoronic full name, and more important: because it can be used on any object.
Function.usesSuper(func)
Ah, "uses super" I can understand. Sean's "isSuper" name did not make sense to me.
however, WRT usesSuper, I isn't clear if it is anymore necessary than usesThis, hasNestedFunctions, hasIfStatements, and potentially hundreds of reflective queries that might be made about a function.
Agreed in general. YAGNI, when in doubt, leave it out.
But Sean was probably thinking that such a "uses super" predicate would be needed by any framework trying to reparent or borrow methods. Such frameworks exist today. Should they all be ported to use defineMethod, blindly?
On Jun 25, 2011, at 6:10 PM, Brendan Eich wrote:
On Jun 24, 2011, at 1:00 PM, Allen Wirfs-Brock wrote:
If there was a mechanism for lexically addressing this, I would expect |super| to track |this| in parallel. From a value perspective, |super| is just a synonym for |this|.
This is an important point, although what is a non-value perspective in JS?
Perhaps I should have said binding perspective.
There are no explicit types. The answer must be an implicit type, the superclass view provided by the [[Prototype]] of the class prototype or ad-hoc containing object in which the method using 'super' was written.
What you are concerned about here may be too subtle for some readers. Any caching of a |super| based property lookup needs to be keyed by the internal type of the object where the property lookup actually starts rather than the internal type of |this|.
Just a +1 here, and a request to make the proposal language support this as clearly (and up-front in the page) as possible.
Yes, I'll have an expanded proposal soon.
On Jun 25, 2011, at 10:31 AM, Allen Wirfs-Brock wrote:
On Jun 25, 2011, at 6:10 PM, Brendan Eich wrote:
On Jun 24, 2011, at 1:00 PM, Allen Wirfs-Brock wrote:
If there was a mechanism for lexically addressing this, I would expect |super| to track |this| in parallel. From a value perspective, |super| is just a synonym for |this|.
This is an important point, although what is a non-value perspective in JS?
Perhaps I should have said binding perspective.
"binding" is a much abused word, but in JS communities I hope we can keep it restricted to names in environments, lexical vs. dynamic vs. argument value to formal parameter binding -- at most!
But I get what you mean. 'super' by itself is a reference to the same object 'this' denotes, or undefined where 'this' is undefined. 'super' and 'this' are aliases. But 'super'-based property references start from a different prototype chain head.
There are no explicit types. The answer must be an implicit type, the superclass view provided by the [[Prototype]] of the class prototype or ad-hoc containing object in which the method using 'super' was written.
What you are concerned about here may be too subtle for some readers. Any caching of a |super| based property lookup needs to be keyed by the internal type of the object where the property lookup actually starts rather than the internal type of |this|.
Yes, I'm concerned about subtlety if 'super' in functions. The tension is between confining 'super' to be valid only in class methods, vs. making it work in any function. But the class-methods-only approach leads to invoke-only methods, to avoid the reparenting or borrowing problem that Object.defineMethod addresses.
I'm less concerned about 'super' for all functions, plus Object.defineMethod. This is "JavaScript-y" and causes me less concern than a class-based 'super' confinement attempt. But the whole package deal still causes concern, because of the "you forgot to use Object.defineMethod" problem. We're adding another runtime error case, a hazard requiring test coverage.
Not sure what can be done about this.
I don’t know if this has been discussed, but I find the |super| work-around provided by YUI and CoffeeScript (in the generated code) not that bad:
ThisClass.__super__.foo.call(this, arg1, arg2);
With ThisClass.super pointing to SuperClass.prototype.
As a completely static solution, one could maybe expand a super-call super.bar(x, y); to CurrentObject.[[Prototype]].bar.call(this, x, y);
On the other hand, I don’t know if a reference to CurrentObject is ever available for some kind of static expansion.
On Jun 25, 2011, at 11:13 AM, Axel Rauschmayer wrote:
I don’t know if this has been discussed, but I find the |super| work-around provided by YUI and CoffeeScript (in the generated code) not that bad:
ThisClass.__super__.foo.call(this, arg1, arg2);
I'm not sure what you mean. If we add 'super', surely we want more than the super hack. We also want a callable expression 'super' in constructors, and a 'super.foo' form for method and other property accesses from subclass methods. We don't want people writing out .call by hand.
With ThisClass.super pointing to SuperClass.prototype.
The CoffeeScript compiler can lexically bind ThisClass and wire up super with generated runtime helper code. That's all backstage of the CoffeeScript language, though.
As a completely static solution, one could maybe expand a super-call super.bar(x, y); to CurrentObject.[[Prototype]].bar.call(this, x, y);
On the other hand, I don’t know if a reference to CurrentObject is ever available for some kind of static expansion.
Are you talking about |here| again, with a new name? There is no static counterpart other than the one Allen proposed, the object literal directly containing the method that uses 'super'.
On 26.06.2011 0:49, Brendan Eich wrote:
On Jun 25, 2011, at 11:13 AM, Axel Rauschmayer wrote:
I don’t know if this has been discussed, but I find the |super| work-around provided by YUI and CoffeeScript (in the generated code) not that bad:
ThisClass.__super__.foo.call(this, arg1, arg2);
I'm not sure what you mean. If we add 'super', surely we want more than the super hack. We also want a callable expression 'super' in constructors, and a 'super.foo' form for method and other property accesses from subclass methods. We don't want people writing out .call by hand.
Apologize in advance if I'm mentioning already discussed thing in these
classes-related topics (unfortunately hadn't enough of time to read them
all and follow), but from what I can say, people want to write also and
mostly just super
, not even super.foo
(though, of course
super.foo
notation would be also nice to have -- to be able to call
any parent method).
Again, we even now (vis simple JS class-library) can write normal
this.super(...)
calls from any descendant method (and could even in
ES3 era, though, via this._super(...)
) -- that is without specifying
exact parent name (i.e. super.foo
) which in case of the same method is
just again is the syntactic noise. So it's important thing to have
just super(...) -- if it's a sugar, let it be really the sugar
(because if not, why do I need it at all if I can achieve even better
with a library?). And in addition -- super.otherMethodName notation.
Dmitry.
CoffeeScript demo:
host-2-189:coffee-script brendaneich$ cat /tmp/super.coffee class B constructor: (x) -> @x = x m: (z) -> @x = z * z
class D extends B constructor: (x, y) -> super x @y = y m: (z) -> 1 + super z
d = new D(3, 4) console.log d.m(5) f = d.m console.log f(6) o = {m:f} console.log o.m(7)
host-2-189:coffee-script brendaneich$ ./bin/coffee -p /tmp/super.coffee (function() { var B, D, d, f, o; var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.super = parent.prototype; return child; }; B = (function() { function B(x) { this.x = x; } B.prototype.m = function(z) { return this.x = z * z; }; return B; })(); D = (function() { __extends(D, B); function D(x, y) { D.super.constructor.call(this, x); this.y = y; } D.prototype.m = function(z) { return 1 + D.super.m.call(this, z); }; return D; })(); d = new D(3, 4); console.log(d.m(5)); f = d.m; console.log(f(6)); o = { m: f }; console.log(o.m(7)); }).call(this);
Note how references to lexically-bound closure function D are captured by the methods that use D.super. This is equivalent to Allen's proposal. Here's the program's output:
host-2-189:coffee-script brendaneich$ ./bin/coffee /tmp/super.coffee 26 37 50
Extracting f = d.m and reparenting f as o.m, calling either f() or o.m(), doesn't change anything.
I don’t know if this has been discussed, but I find the |super| work-around provided by YUI and CoffeeScript (in the generated code) not that bad:
ThisClass.__super__.foo.call(this, arg1, arg2);
I'm not sure what you mean. If we add 'super', surely we want more than the super hack. We also want a callable expression 'super' in constructors, and a 'super.foo' form for method and other property accesses from subclass methods. We don't want people writing out .call by hand.
Agreed. As a hack, it already accomplishes a lot of what |super| is supposed to, mainly not hardcoding the name of the super-class.
With ThisClass.super pointing to SuperClass.prototype.
The CoffeeScript compiler can lexically bind ThisClass and wire up super with generated runtime helper code. That's all backstage of the CoffeeScript language, though.
As a completely static solution, one could maybe expand a super-call super.bar(x, y); to CurrentObject.[[Prototype]].bar.call(this, x, y);
On the other hand, I don’t know if a reference to CurrentObject is ever available for some kind of static expansion.
Are you talking about |here| again, with a new name?
Sorry for the confusion. ;-)
I think there is a subtle difference between the two (at least as I use them):
-
|here|: dynamic, computed as part of the property lookup, closely related to |this|.
-
CurrentObject (or possibly a better name): Static, only available inside an object literal.
There is no static counterpart other than the one Allen proposed, the object literal directly containing the method that uses 'super'.
That would be enough. I guess I’m suggesting then to implement “super” like CoffeeScript does. Wouldn’t that be simpler?
On Jun 25, 2011, at 2:01 PM, Dmitry A. Soshnikov wrote:
On 26.06.2011 0:49, Brendan Eich wrote:
On Jun 25, 2011, at 11:13 AM, Axel Rauschmayer wrote:
I don’t know if this has been discussed, but I find the |super| work-around provided by YUI and CoffeeScript (in the generated code) not that bad:
ThisClass.__super__.foo.call(this, arg1, arg2);
I'm not sure what you mean. If we add 'super', surely we want more than the super hack. We also want a callable expression 'super' in constructors, and a 'super.foo' form for method and other property accesses from subclass methods. We don't want people writing out .call by hand.
Apologize in advance if I'm mentioning already discussed thing in these classes-related topics (unfortunately hadn't enough of time to read them all and follow), but from what I can say, people want to write also and mostly just
super
, not evensuper.foo
(though, of coursesuper.foo
notation would be also nice to have -- to be able to call any parent method).
In a constructor, calling super calls the super-constructor. No need for super.constructor() or whatever.
Unlike CoffeeScript, which allows a method foo to super-call only the method of the same name in the superclass, the ES.next proposals I've seen want super.foo(). This allows super.bar() from method foo of a subclass, where the superclass bar might of course loop around and call the superclass foo.
Again, we even now (vis simple JS class-library) can write normal
this.super(...)
calls from any descendant method (and could even in ES3 era, though, viathis._super(...)
) -- that is without specifying exact parent name (i.e.super.foo
) which in case of the same method is just again is the syntactic noise. So it's important thing to have just super(...) -- if it's a sugar, let it be really the sugar (because if not, why do I need it at all if I can achieve even better with a library?). And in addition -- super.otherMethodName notation.
For some reason I missed this Ruby-ish notion of super-method calling, which CoffeeScript supports. It is not what the ES.next proposals require for a super-method call. See
harmony:classes#member_delegation
We should discuss this more concise 'super'-only, not 'super.foo' idea. Is it only for conciseness, or is the fact that it does not support 'super.bar' from within a method named 'foo' important?
On Jun 25, 2011, at 2:05 PM, Axel Rauschmayer wrote:
- CurrentObject (or possibly a better name): Static, only available inside an object literal.
This is not proposed, and we don't want to require Object.getPrototypeOf(CurrentObject), or any name with the evil __ bracketing, for that matter. What is proposed, static super + defineMethod to clone the method into a new object with a rebound 'super', is enough.
There is no static counterpart other than the one Allen proposed, the object literal directly containing the method that uses 'super'.
That would be enough. I guess I’m suggesting then to implement “super” like CoffeeScript does. Wouldn’t that be simpler?
As my next message showed, Allen's harmony:object_initialiser_super is like CoffeScript as far as dynamic vs. lexical binding goes.
It differs in the same way that the Harmony classes proposal differs from CoffeeScript: you call super.foo() or super.bar() from method foo. You cannot just call super() from method foo to invoke the superclass's method foo, and only that method.
Should we really constrain super calls in methods to be only of the same-named method in the superclass? Conciseness is better but the restriction seems worse. Is it necessary in Ruby or CoffeeScript?
On Jun 25, 2011, at 2:10 PM, Brendan Eich wrote:
Should we really constrain super calls in methods to be only of the same-named method in the superclass? Conciseness is better but the restriction seems worse. Is it necessary in Ruby or CoffeeScript?
Consider also that the ES.next 'super' proposals allow super.baz where super.baz is a property other than a method. That would not be possible with 'super'-only.
That would be enough. I guess I’m suggesting then to implement “super” like CoffeeScript does. Wouldn’t that be simpler?
As my next message showed, Allen's harmony:object_initialiser_super is like CoffeScript as far as dynamic vs. lexical binding goes.
It differs in the same way that the Harmony classes proposal differs from CoffeeScript: you call super.foo() or super.bar() from method foo. You cannot just call super() from method foo to invoke the superclass's method foo, and only that method.
Should we really constrain super calls in methods to be only of the same-named method in the superclass? Conciseness is better but the restriction seems worse. Is it necessary in Ruby or CoffeeScript?
Oh. I have never seen super used like in CoffeeScript before (which is why I haven’t even noticed it in the CoffeeScript code that I have looked at, so far). I am strongly in favor of the more capable Harmony solution.
I guess the difference between CoffeeScript and Harmony is that CS knows the name of the class inside a class body during compilation, whereas in an object literal, you need to attach a property to a method which points to the object. By looking at JS code generated from CS code, it seemed that the CS approach was somehow simpler, but that was an illusion.
On 26.06.2011 1:06, Brendan Eich wrote:
On Jun 25, 2011, at 2:01 PM, Dmitry A. Soshnikov wrote:
On 26.06.2011 0:49, Brendan Eich wrote:
On Jun 25, 2011, at 11:13 AM, Axel Rauschmayer wrote:
I don’t know if this has been discussed, but I find the |super| work-around provided by YUI and CoffeeScript (in the generated code) not that bad:
ThisClass.__super__.foo.call(this, arg1, arg2);
I'm not sure what you mean. If we add 'super', surely we want more than the super hack. We also want a callable expression 'super' in constructors, and a 'super.foo' form for method and other property accesses from subclass methods. We don't want people writing out .call by hand.
Apologize in advance if I'm mentioning already discussed thing in these classes-related topics (unfortunately hadn't enough of time to read them all and follow), but from what I can say, people want to write also and mostly just
super
, not evensuper.foo
(though, of coursesuper.foo
notation would be also nice to have -- to be able to call any parent method). In a constructor, calling super calls the super-constructor. No need for super.constructor() or whatever.
Yeah, and constructor is just another method, the same as others; it
just initializes the instance. Why from this viewpoint not to allow the
user to call a parent method with the same name from another method,
e.g. foo
?
Unlike CoffeeScript, which allows a method foo to super-call only the method of the same name in the superclass, the ES.next proposals I've seen want super.foo(). This allows super.bar() from method foo of a subclass, where the superclass bar might of course loop around and call the superclass foo.
Yes, I understand and also noticed that it's a good addition -- to be
able call any parent method from another method (e.g. super.bar
from
foo
). Moreover, this strategy is used in many other languages.
E.g. PHP also uses "full notation" everywhere, even for __construct
D = (function() { __extends(D, B); function D(x, y) { D.super.constructor.call(this, x); this.y = y; } D.prototype.m = function(z) { return 1 + D.super.m.call(this, z); }; return D; })();
I wonder if using an IIFE isn’t counter-productive here:
- D.super refers to the binding of D inside the IIFE which means that the IIFE environment is will be kept alive.
On 26.06.2011 1:29, Axel Rauschmayer wrote:
That would be enough. I guess I’m suggesting then to implement “super” like CoffeeScript does. Wouldn’t that be simpler? As my next message showed, Allen's harmony:object_initialiser_super is like CoffeScript as far as dynamic vs. lexical binding goes.
It differs in the same way that the Harmony classes proposal differs from CoffeeScript: you call super.foo() or super.bar() from method foo. You cannot just call super() from method foo to invoke the superclass's method foo, and only that method.
Should we really constrain super calls in methods to be only of the same-named method in the superclass? Conciseness is better but the restriction seems worse. Is it necessary in Ruby or CoffeeScript?
Oh. I have never seen super used like in CoffeeScript before
JFTR: Java, Ruby, some others I'm sure (have to recall though these "others"). It's convenient, why not? Do you want to repeat every time this the same name even if you are sure that you call exactly needed, this, method?
(which is why I haven’t even noticed it in the CoffeeScript code that I have looked at, so far). I am strongly in favor of the more capable Harmony solution.
I guess the difference between CoffeeScript and Harmony is that CS knows the name of the class inside a class body during compilation,
Yes, but we can write in JS this.super(...) and w/o compile time (though, not so efficient).
Dmitry.
On Jun 25, 2011, at 2:29 PM, Axel Rauschmayer wrote:
That would be enough. I guess I’m suggesting then to implement “super” like CoffeeScript does. Wouldn’t that be simpler?
As my next message showed, Allen's harmony:object_initialiser_super is like CoffeScript as far as dynamic vs. lexical binding goes.
It differs in the same way that the Harmony classes proposal differs from CoffeeScript: you call super.foo() or super.bar() from method foo. You cannot just call super() from method foo to invoke the superclass's method foo, and only that method.
Should we really constrain super calls in methods to be only of the same-named method in the superclass? Conciseness is better but the restriction seems worse. Is it necessary in Ruby or CoffeeScript?
Oh. I have never seen super used like in CoffeeScript before (which is why I haven’t even noticed it in the CoffeeScript code that I have looked at, so far). I am strongly in favor of the more capable Harmony solution.
There are deeper waters here. See www.artima.com/intv/nonvirtualP.html (via 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.
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."
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.
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.
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."
I guess the difference between CoffeeScript and Harmony is that CS knows the name of the class inside a class body during compilation, whereas in an object literal, you need to attach a property to a method which points to the object.
To the object's [[Prototype]], if you mean the internal property referenced by 'super' in the method.
By looking at JS code generated from CS code, it seemed that the CS approach was somehow simpler, but that was an illusion.
Looks like more code in the generated case to me. More != simpler, even if it may be no more complex.
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.
On Jun 25, 2011, at 2:37 PM, Axel Rauschmayer wrote:
D = (function() { __extends(D, B); function D(x, y) { D.super.constructor.call(this, x); this.y = y; } D.prototype.m = function(z) { return 1 + D.super.m.call(this, z); }; return D; })();
I wonder if using an IIFE isn’t counter-productive here:
- D.super refers to the binding of D inside the IIFE which means that the IIFE environment is will be kept alive.
It's no big deal. OTOH leaking the name seems like it could cause trouble.
On Jun 25, 2011, at 2:37 PM, Dmitry A. Soshnikov wrote:
We should discuss this more concise 'super'-only, not 'super.foo' idea. Is it only for conciseness, or is the fact that it does not support 'super.bar' from within a method named 'foo' important?
Of course it's mostly for conciseness.
I don't think it's just about conciseness. See my last message.
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.
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.
It probably makes sense to distinguish between preventing overriding and static dispatch:
- AFAIK, Java always does dynamic dispatch and has virtual instance methods. final “merely” prevents overriding. But that does indeed reduce the hooks that Hejlsberg talks about.
- With static dispatch, you can override which also might lead to unintended effects.
The problem seems to be that a public method plays two roles: It is an interface to the clients of a class, but also an interface to the methods of the class itself. In Java, when I want a method to exclusively play the latter role then I make it private. Maybe a method should never call sibling public methods, only private ones.
I’ve done overriding to contribute to an API, but also to patch something (to avoid copy-pasting code – a hack, but still needed). The former is part of the design, the latter is necessary for use cases that cannot be predicted. I’m less paranoid about the wrong method being overridden than about a method that one wants to be overridden not being called from the overriding method. That is, I’m usually paranoid about clients not adhering to the protocol of my API. Thus, I like anything that helps me with expressing the protocol and ensuring that no mistakes are made. I only want to enforce something if it helps the previous two goals. In this vein, “private by naming convention” is enough for me. Obviously, different rules apply when security is a concern.
How about a marker saying “this method can be overridden” (enable, don’t prevent)? If a method does not have this marker it could still be overridden, but one would have to expend extra effort to prove that one knows what one is doing (similar to suppressing generics-related type warnings in Java).
I’m not sure how much of the above applies to JavaScript, though, because you need dynamic dispatch to make shared methods work. And things being so simple and flexible is part of JavaScript’s charm.
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."
A very good point. AFAIK, Common Lisp only has next-method calls and no generic super calls. [As an aside, from what I have seen of Common Lisp, it is a great mix of dynamic language features and static language features.]
What property name would be be used in such implicit super calls? A function doesn't know what property name was used to access it. Using an implicit property name in a super call would require that every method call implicitly pass the name used to access the property. This is just like the |here| problem.
On Jun 25, 2011, at 10:42 PM, Brendan Eich wrote:
There are deeper waters here. See www.artima.com/intv/nonvirtualP.html (via 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, 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 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 (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);
On Jun 26, 2011, at 3:05 AM, Allen Wirfs-Brock wrote:
What property name would be be used in such implicit super calls? A function doesn't know what property name was used to access it.
In the languages and systems that have super()-only, a method always has a name.
So we could define "method that can use super" narrowly, as the new syntax in object initialiser, or in class body. IIRC you clarified that any nested function expression with such a method doesn't get the same super -- indeed gets undefined (I think; still some questions here related to |this| binding).
Using an implicit property name in a super call would require that every method call implicitly pass the name used to access the property. This is just like the |here| problem.
No, not if the compiler sees the method name and burns it into the function object. Then it's just an internal property, analogous to static super. This is what engines do today with named function forms (definitions, named function expressions).
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 www.artima.com/intv/nonvirtualP.html (via 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, 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 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 (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.
On Jun 26, 2011, at 9:29 AM, Brendan Eich wrote:
On Jun 26, 2011, at 3:05 AM, Allen Wirfs-Brock wrote:
What property name would be be used in such implicit super calls? A function doesn't know what property name was used to access it.
In the languages and systems that have super()-only, a method always has a name.
So we could define "method that can use super" narrowly, as the new syntax in object initialiser, or in class body. IIRC you clarified that any nested function expression with such a method doesn't get the same super -- indeed gets undefined (I think; still some questions here related to |this| binding).
Using an implicit property name in a super call would require that every method call implicitly pass the name used to access the property. This is just like the |here| problem.
No, not if the compiler sees the method name and burns it into the function object. Then it's just an internal property, analogous to static super. This is what engines do today with named function forms (definitions, named function expressions).
I don't follow what you're saying here --
SomePrototype.foo = function() { super() }
Where is the function name hardcoded? Certainly JSC makes no distinction in behaviour of function expressions in any context. The only time a function object created by a function expression will have a name, is when the optional name is provided. That then raises what super() is in the case of
SomePrototype.foo = function bar() { super() }
When this thread initially started my thought was "yes that sounds great!" but i very rapidly came to the conclusion that non-lexical super just isn't workable without introducing inefficiencies in all call sites regardless of whether the dynamic-super feature is used.
Using an implicit property name in a super call would require that every method call implicitly pass the name used to access the property. This is just like the |here| problem.
No, not if the compiler sees the method name and burns it into the function object. Then it's just an internal property, analogous to static super. This is what engines do today with named function forms (definitions, named function expressions).
I don't follow what you're saying here --
SomePrototype.foo = function() { super() }
Where is the function name hardcoded? Certainly JSC makes no distinction in behaviour of function expressions in any context. The only time a function object created by a function expression will have a name, is when the optional name is provided. That then raises what super() is in the case of
SomePrototype.foo = function bar() { super() }
When this thread initially started my thought was "yes that sounds great!" but i very rapidly came to the conclusion that non-lexical super just isn't workable without introducing inefficiencies in all call sites regardless of whether the dynamic-super feature is used.
This would only work inside an object literal (similar to |super|, where you need to know about |here|, the owning object). Additionally, Allen’s Object.defineMethod already has a parameter with the method name, so it would work there, too.
On Jun 26, 2011, at 10:49 AM, Oliver Hunt wrote:
On Jun 26, 2011, at 9:29 AM, Brendan Eich wrote:
In the languages and systems that have super()-only, a method always has a name.
So we could define "method that can use super" narrowly, as the new syntax in object initialiser, or in class body.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (Note above underlined bit.)
No, not if the compiler sees the method name and burns it into the function object. Then it's just an internal property, analogous to static super. This is what engines do today with named function forms (definitions, named function expressions).
I don't follow what you're saying here --
SomePrototype.foo = function() { super() }
See above -- I was suggesting restricting super to certain contexts: methods in classes and object initialisers only. Just a thought.
On Jun 26, 2011, at 10:54 AM, Axel Rauschmayer wrote:
This would only work inside an object literal
or in a method in a class.
(similar to |super|, where you need to know about |here|, the owning object). Additionally, Allen’s Object.defineMethod already has a parameter with the method name, so it would work there, too.
Allowing 'super' in any function, requiring Object.defineMethod to be used with an object and a name for that function to be callable and survive its first super reference, is plausible and as you note, there's a name to burn into the function if necessary.
But I still wonder if we wouldn't be better off restricting where super can occur. I can't prove it, but we are following in the universal-'this' footsteps (but with static or else Object.defineMethod binding). That sounds a warning bell in my head.
On Jun 26, 2011, at 8:48 PM, Brendan Eich wrote:
But I still wonder if we wouldn't be better off restricting where super can occur. I can't prove it, but we are following in the universal-'this' footsteps (but with static or else Object.defineMethod binding). That sounds a warning bell in my head.
While I highly support improving ECMAScript's declarative mechanisms for defining object abstractions I'd be pretty concerned if we had object abstraction forms that can only be created declaratively. The ability to use reflection to construct such abstractions has already proven its worth both in JS and in other languages. Having forms of methods that can't be created via reflection seems a step backwards. BTW there are other proposals floating around that also have this characteristic, for example perhaps some of the private property proposals. We need to be concerned about those too.
It is important to find a good balance between power and bullet-proof footwear. But, intentionally restricting an imperatively constructed object from using |super| in order to prevent foot shooting seems very un-JS like.
More tomorrow, goodnight.
On Jun 26, 2011, at 3:05 PM, Allen Wirfs-Brock wrote:
On Jun 26, 2011, at 8:48 PM, Brendan Eich wrote:
But I still wonder if we wouldn't be better off restricting where super can occur. I can't prove it, but we are following in the universal-'this' footsteps (but with static or else Object.defineMethod binding). That sounds a warning bell in my head.
While I highly support improving ECMAScript's declarative mechanisms for defining object abstractions I'd be pretty concerned if we had object abstraction forms that can only be created declaratively. The ability to use reflection to construct such abstractions has already proven its worth both in JS and in other languages. Having forms of methods that can't be created via reflection seems a step backwards.
I agree, but I'm not talking about such a declarative-only, no-imperative restriction as such. Rather, an essentially grammatical restriction on where 'super' can be used. But I'm not selling it, just discussing it, so don't worry.
BTW there are other proposals floating around that also have this characteristic, for example perhaps some of the private property proposals. We need to be concerned about those too.
Not the only one that matters:
It has only imperative API via a built-in module. So if anything, it's missing declarative syntax.
It is important to find a good balance between power and bullet-proof footwear. But, intentionally restricting an imperatively constructed object from using |super| in order to prevent foot shooting seems very un-JS like.
I know, I think I said that too :-P. That is, JS has the bamboo tube, saltpeter, and other raw ingredients like unbound 'this' projectiles for James Tiberius Kirk, captain of JS hacking, to assemble a bazooka the hard way, to conquer the big lizard-alien programming problems. But lesser mortals sometimes have it blow up in their faces, shoot backwards, take off a leg, etc.
Gotta admit, universal 'super' with defineMethod hangs together -- it would be easy to implement.
More tomorrow, goodnight.
Goodnight!
On Jun 26, 2011, at 11:44 PM, Brendan Eich wrote:
On Jun 26, 2011, at 3:05 PM, Allen Wirfs-Brock wrote:
On Jun 26, 2011, at 8:48 PM, Brendan Eich wrote:
But I still wonder if we wouldn't be better off restricting where super can occur. I can't prove it, but we are following in the universal-'this' footsteps (but with static or else Object.defineMethod binding). That sounds a warning bell in my head.
While I highly support improving ECMAScript's declarative mechanisms for defining object abstractions I'd be pretty concerned if we had object abstraction forms that can only be created declaratively. The ability to use reflection to construct such abstractions has already proven its worth both in JS and in other languages. Having forms of methods that can't be created via reflection seems a step backwards.
I agree, but I'm not talking about such a declarative-only, no-imperative restriction as such. Rather, an essentially grammatical restriction on where 'super' can be used. But I'm not selling it, just discussing it, so don't worry.
I particular don't want to discover that we have forced programmer into doing things like:
let obj = eval(sup<|{ method1 () {super.method1()} }; ...
to imperatively construct an object that has methods that reference super.
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.