Referencing `super`

# Brett Andrews (10 years ago)

Some differences/oddities I noticed between referencing and invoking super. I briefly discussed this with Erik Arvidsson at google/traceur-compiler#1220. In essence, if you can invoke in two seperate ways, it seems logical that you should be able to reference in those two ways (super() ~= super.submit(); super != super.submit).

class ClientForm extends Form{
  submit() {
    super.submit(); // Invokes Form.submit
    let superSubmit = super.submit; // Reference to Form.submit
    superSubmit(); // Invokes, but `this` is now undefined; not sure if intended

    super(); // Invokes Form.submit
    let superSubmit2 = super; // Error: "Unexpected token ;"
}
# Allen Wirfs-Brock (10 years ago)
class ClientForm extends Form{
  submit() {
    super.submit(); // Invokes Form.submit
    let superSubmit = super.submit; // Reference to Form.submit
    superSubmit(); // Invokes, but `this` is now undefined; not sure if intended

just like:

this.submit(); // Invokes ClientForm.submit
let thisSubmit = this.submit;  //Reference to ClientForm.submit
thisSubmit();   //invokes, but 'this' in now undefined

This is how properties and method invocations work in JS. All that the use of 'super' does is change the place the property lookup starts. Otherwise super is equivalent to this in the above code.

super(); // Invokes Form.submit

semantically equivalent to super.submit(); just a short cut

let superSubmit2 = super; // Error: "Unexpected token ;"

in some languages, such a unqualified 'super' reference would be equivalent to 'this'. We intentionally made it an error for that reason. I perhaps could be convinced that it should mean the same as super.submit. But in that case,

superSubmit2()

would still not be the same thing as

super();

or

super.submit();
# Brett Andrews (10 years ago)

Thanks for your response, Allen. I'm not sure what convincing I can do. To me it seems odd that super() is the same as super.submit() but super is not the same as super.submit, but perhaps to others that seems perfectly fine. An alternative to this suggestion would be super() should not be the same as super.submit(); and instead super() is either illegal or calls constructor.

# Domenic Denicola (10 years ago)

I sympathize; I have always found the fact that bare super() works to be confusing.

# Rick Waldron (10 years ago)

When a bare super() call appears in a method (whether constructor or not) it can only have one meaning and that's a call to a method of the same name in the parent class. This isn't particularly innovative: John Resig's Simple JavaScript Inheritance—arguably one of the most widely used (many clones, forks and spin-offs exist) "abstract class" techniques—provides this._super() which does the same thing that ES6 super() does. This pattern existed before and has been repeated throughout many libraries that have stood out over the years: Prototype, Dojo, Ext.js and certainly others. CoffeeScript implements super() this way as well.

# Rick Waldron (10 years ago)

On Wed, Aug 6, 2014 at 12:38 AM, Rick Waldron <waldron.rick at gmail.com> wrote:

This isn't particularly innovative:

Note: I don't mean to imply that any of the examples I gave here are innovative either, they all copy the design (in a quasi-super-via-API way) and semantics that exist in other languages and I'd bet specifically Ruby. (Python requires the fully qualified name, which should be proof that JS has it right by allowing both forms)

# Brett Andrews (10 years ago)

My preference would be for super() to remain as-is, and give super the equivalent semantics: reference a method of the same name in the parent class (as opposed to invoke). I only provided the alternative since my ultimate view is that super and super() should function similarly (either both be legal, or both be illegal).

# Allen Wirfs-Brock (10 years ago)

use of unqualified super in this manner has a long history in OO programming language. It’s too late this evening for me to dig up references for you but it dates to at least language designs of the early 1980. Smalltalk didn’t do it, but mainly because it does doesn’t use a function invocation notation for method method calls.

The the early Smalltalk develpment experience clearly showed that methods almost always do super invokes of the same method name as the caller. Unqualified super is a shorthand for this most common. It is so common, that I always react to seeing a qualified super invocation as a very exceptional case that I should look very closely at to understand why a something other than the current method is being super invoked.

The need to qualify super with a different method name is so rare, that some languages support nothing but unqualified super. But there are a few situations were it is quite useful and it is worth having in an OO programming language. It’s kind of ironic that I recall previous discussions during the development of the ES6 class design where I had to justify why we should allow super to be qualified with a property name.

# Allen Wirfs-Brock (10 years ago)

On Aug 5, 2014, at 10:11 PM, Brett Andrews <brett.j.andrews at gmail.com> wrote:

My preference would be for super() to remain as-is, and give super the equivalent semantics: reference a method of the same name in the parent class (as opposed to invoke). I only provided the alternative since my ultimate view is that super and super() should function similarly (either both be legal, or both be illegal).

I addressed this in my first replay

On Aug 5, 2014, at 6:37 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

in some languages, such a unqualified 'super' reference would be equivalent to 'this'.

We intentionally made it an error for that reason. I perhaps could be convinced that it should mean the same as 'super.submit’.

But it isn’t something we necessarily need to consider for ES6. We can always relate the error if we find that people really want to do that. I suspect we won’t.

expressions like:

super = expr;

are probably good examples of DDWIDM. (Don’t Do What I Didn’t Mean)

in a method named ‘foo’

super = expr;

would be equivalent to

this.foo = expr;

if ‘foo' was not already defined as an access property (and methods usually are data properties). Quite likely something the programmer didn’t mean.

# Brendan Eich (10 years ago)

Rick Waldron wrote:

When a bare super() call appears in a method (whether constructor or not) it can only have one meaning and that's a call to a method of the same name in the parent class. This isn't particularly innovative: John Resig's Simple JavaScript Inheritance[0]—arguably one of the most widely used (many clones, forks and spin-offs exist) "abstract class" techniques—provides this._super() which does the same thing that ES6 super() does. This pattern existed before and has been repeated throughout many libraries that have stood out over the years: Prototype, Dojo, Ext.js and certainly others. CoffeeScript implements super() this way as well.

CoffeeScript imitated Ruby here.

Smalltalk had super rather than self sends, but you had to send with the full selector (think method name). Java has super() in constructors but requires super.method() in methods. I'm cool with super() in methods, I forgot we disallowed naked super, and my gut says we would support it as equivalent to this.

# Christoph Martens (10 years ago)

On 06.08.2014 06:38, Rick Waldron wrote:

When a bare super() call appears in a method (whether constructor or not) it can only have one meaning and that's a call to a method of the same name in the parent class. This isn't particularly innovative: John Resig's Simple JavaScript Inheritance—arguably one of the most widely used (many clones, forks and spin-offs exist) "abstract class" techniques—provides this._super() which does the same thing that ES6 super() does. This pattern existed before and has been repeated throughout many libraries that have stood out over the years: Prototype, Dojo, Ext.js and certainly others. CoffeeScript implements super() this way as well.

I wonder why you chose that way in the spec. An issue I can see here is that mixin-based inheritance on a per-method basis won't work properly as expected.

In my game engine lycheeJS, I'm using a mixin based inheritance to allow

  1. inclusions based on feature detection (html, nodejs, v8gl, etc.)
  2. inclusions in a specified order (a la a extends b extends c)
  3. overwrites of your method with super calls to both via direct b.prototype (or c.prototype) access
  4. mixin based includes that allow inclusions of single methods of other classes

Simplified Example:

var a = function(settings) {
   // moar stuff
   b.call(this,settings);
   c.call(this,settings);
};

a.prototype = { render: function() {
   c.prototype.render.call(this);
   doCustomStuffInBetween();
   b.prototype.render.call(this);
}};

The last reasons (c, d) are very important to my game engine, because it allows inclusion of single "public" methods. For example, you can reuse a 2D render() method of a ui.State for rendering the UI layer, then draw something in 3D on top of it via the game.State.

To access those namespaces properly, I have to create all definitions inside a closure with "dynamic" arguments. For example:

lychee.define('lychee.Renderer').exports(function(lychee, global, attachments) {});
lychee.define('game.Renderer').exports(function(lychee, game, global, attachments) {});

That allows me some essential things for the engine concept:

  1. every definition can be exported in a sandbox
  2. theoretically sandboxes of supports() (feature detection callbacks) are possible with new Proxies, currently they have to be executed global (different story of detecting type-based access to properties that are not existing at runtime and need to be dispatched)
  3. every definition can be serialized as a string and deserialized later, in sandboxed state without requiring access to global properties
  4. global accesses to properties or bound variables from outer scopes are easily traceable via try/catch
  5. global can be faked, it is not existing and not "window", it's a reference to lychee.Environment's global property.

An example of this mixin-based inheritance usage can be found here:

LazerUnicorns/lycheeJS/blob/development-0.8/projects/boilerplate/source/entity/Circle.js#L99

PS: If you are curious, install lycheeJS via README.md and run the localhost:8080/projects/boilerplate. Then call lychee.environment.serialize(); which creates the JSON that is deserializable (and injectable) at runtime.

Also, what is REALLY important (I can't stress that enough). JavaScript has a huge bonus when being compared to other programming languages. Namespaces are just objects in global scope that can exist at runtime, not necessarily at compile time or definition time. Access to lychee.game.Main is just new lychee.Environment().global.lychee.game.Main. The advantage here is that global can be faked as a sandbox and can be isolated to trace errors occuring on a per-definition base at runtime. (Namely remote debugging and remote code injection via websockets)

This leads to the possibility of inclusion of different libraries under a different namespace. All libraries think they have say the "game" namespace, but in fact, you can include them in a different project under a custom namespace. Every library, if sandboxed, will never pollute the "global namespace".

These features were the killer decision for me to use JavaScript as a game engine base, otherwise I probably would've used Lua (I'm honest).

I would recommend having those listed features in mind when talking about super() and namespaces, because the current spec still will prevent me from using native "class" keyword in JavaScript.

If newbies ask me if they should use classes, I say no to them, because I still think the featureset of a mixin-based inheritance (leading to fancy prototype copying and sealing) is still better.

# Kevin Smith (10 years ago)

I would recommend having those listed features in mind when talking about super() and namespaces, because the current spec still will prevent me from using native "class" keyword in JavaScript.

If newbies ask me if they should use classes, I say no to them, because I still think the featureset of a mixin-based inheritance (leading to fancy prototype copying and sealing) is still better.

That would be terrible advice, because classes in Javascript are just sugar for setting up a constructor and its associated prototype. Once you define a class, you can do any kind of dynamic tomfoolery that you like, including setting up mixins in the fashion you describe.

# Rick Waldron (10 years ago)

On Wednesday, August 6, 2014, Brendan Eich <brendan at mozilla.org> wrote:

CoffeeScript imitated Ruby here.

Yep, and probably the other quasi-super APIs I mentioned as well—that was in my follow up message.

# Rick Waldron (10 years ago)

On Wed, Aug 6, 2014 at 2:06 AM, Brendan Eich <brendan at mozilla.org> wrote:

Smalltalk had super rather than self sends, but you had to send with the full selector (think method name). Java has super() in constructors but requires super.method() in methods. I'm cool with super() in methods, I forgot we disallowed naked super, and my gut says we would support it as equivalent to this.

To clarify, you don't mean super === this, right?

# Brendan Eich (10 years ago)

Rick Waldron wrote:

To clarify, you don't mean super === this, right?

The alternative is for bare super to denote the same-named superclass method bound to this. That enables the equivalence Allen wrote based on Brett's error citation:

 let superSubmit2 = super; // Error: "Unexpected token ;"
 superSubmit2(); // if no Error, this is equivalent to super()

But that breaks the other equivalence:

 super.method();  ====  do { let s = super; s.method(); }

So you can see why bare super is currently illegal! (Want a better error message than the one Brett showed.)

If we make bare super an error for now, in hopes of resolving this conflict of equivalences later, which way do we think we'll resolve? We ought to have an opinion now.

# Rick Waldron (10 years ago)

Yes, this is/was all clear (and I stand on the side of keeping bare super illegal), I was just curious about what your gut was saying ;)

Agree that a more explicit early error would be ideal.

# Axel Rauschmayer (10 years ago)

On 06 Aug 2014, at 21:03 , Brendan Eich <brendan at mozilla.org> wrote:

If we make bare super an error for now, in hopes of resolving this conflict of equivalences later, which way do we think we'll resolve? We ought to have an opinion now.

Given how much super does under the hood (if you call a method, you get a super/this hybrid), a stand-alone super may give people the wrong idea (and I don’t see how it would be useful). It may be better to provide access to [[HomeObject]] (via Reflect?).

# Brendan Eich (10 years ago)

Rick Waldron wrote:

I was just curious about what your gut was saying ;)

I was going for alias to this. But that argues against super() as short for super.method() in method context. And that means Java, so bleah :-P.

# Brett Andrews (10 years ago)

I don't think we need to do any special binding to this for bare super. Perhaps it will help if I provide the original use case that led me to this. I'm using AngularJS and declaring controllers as classes. I have a base FormCtrl that extends (for example) a ClientFormCtrl. The cut-down version of the constructor is this:

constructor($injector) {
  $injector.invoke(super, this, {
    formName: 'activityUpsertForm'
  });
}

In this case, I don't care what super is bound to, since under the covers Angular is going to do super.apply(this).

I think the most obvious and least confusing/breaking way for super to function is for it to be the equivalent of let super = super.[method_name]

# Brendan Eich (10 years ago)

Brett Andrews wrote:

I think the most obvious and least confusing/breaking way for super to function is for it to be the equivalent of let super = super.[method_name]

(No .[ there, that may be used in the future to mean something else