new instantiation design alternatives

Domenic Denicola domenic at domenicdenicola.com
Sun Sep 14 19:47:16 PDT 2014


I want to give a +1 to Allen. The syntax really is what-you-see-is-what-you-get, which is a great virtue. The C++-derived initializer is cute and tempting, but not nearly as powerful or easy to grok.

That said, I feel weakly that requiring the correct `this = new super(...)`and/or `this = Object.create(new^.prototype)` invocation is a high price to pay for every derived class ever. In particular, "basic constructor functions" (ES5 "classes") that don't wish to use the superclass constructor don't need `this = Object.create(new^.prototype)`, so making ES6 classes require that is strange. It feels like protypal inheritance has "broken" once you start using ES6 classes, and it requires you to fix it by manually doing something that was previously implicit.

I think I'd most be in favor of a third option that implicitly adds `this = Object.create(new^.prototype)` if no `this`-assignment is present. That way, the superclass constructor is never implicitly called, which is kind of what you would expect. But if you do no `this =` assignment, things don't totally break: you still get your prototype and a valid `this`, but you don't inherit the allocation or initialization logic from the superclass. 


From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Allen Wirfs-Brock
Sent: Monday, September 15, 2014 03:54
To: brendan at mozilla.org
Cc: Jeff Morrison; Mark S. Miller; es-discuss
Subject: Re: new instantiation design alternatives


On Sep 14, 2014, at 4:23 PM, Brendan Eich wrote:


Kevin Smith wrote:

If we want to merge allocation with initialization, then I'm going to want to invoke C# (yet again):

   class ColoredPoint extends Point {
       constructor(x, y, color) : super(x, y) {
          this.color = color;
       }
   }

Gotta credt C#'s parents, C++, which would require Point not super.

I think you are really onto something here. We do not want anything looking like an expression-statement in the constructor body, which besides being verbose has unwanted degrees of freedom (requiring TDZ for `this`, running into the dead code problem Jeff cited, etc.).

the dead code problem (and we can debate whether it is actually a significant problem) goes away with alternative 2 (no automatic mew super in derived class constructors. 

The TDZ constrained `this` is an elegant solution which we had a good consensus on at the last TC39 meeting. Since then we have developed has it further unifies various allocation patterns that come up in JS object constructions.  Everybody really need to really work through all the use cases given in the Gists and similarly work through them all with what every other alternatives they may think would be better. 


It sure seems to me that we rather want a special form in the constructor head. This came up in ES4.

the C/C# constructor header approach butts heads with other ES features and isn't expressive for the sort of dynamic classes that ES allows.

In terms of headbutting, consider

`constructor({a: x, b: y), [a1,a2,a3,...arest], c ) : super(??, ??) {}//  what do I put here to super call the constructor with only the first two arguments)`

perhaps:

`constructor{a: x, b: y), [a1,a2,a3,...arest], c ) : super(arguments[0], arguments[1]) {}`

but that means that the special header form must allow arbitrary expressions.  Are those expression in the parameter scope of the body scope.

Most importantly, a single constrained expression isn't expressive enough.  The problem, is that a subclass constructor may need to perform arbitrary compelx computations before super newing the its base constructor.  For example, consider a TypeArray subclass constructor that filters various collections passed to to the derived  constructor to produce a simple list iterator that it passed to the  base constructor.

The constrained super expression is a slippery slope that leads you to a cliff. Better to go with the full generality and expressiveness of the function body.


Separately I agree `this = new super(x, y);` is just crazy long and very likely to be left out in whole or in part (the `new`).

If left-out, programs will fail quickly and noisily (reference errors) especially with the no auto super design alternative.

And this hardly seems like a significant length issue as we are talking about something that at typically occurs only once per subclass with an extends clause. And not even for all subclasses.

and ignoring white space we are taking about either +5 characters ("this=") or 8 characters ("this=new") if you think the alternative is is just "super" without a "new".  But in https://gist.github.com/allenwb/291035fbf910eab8e9a6 we addressed why it is really a bad idea to make "super(a,b)"  be a [[Construct]] operation rather than a [[Call]] operation. Basically, JS is currently very consistent in that `new foo()` and `foo()` do not have the same language level meanings.  It would be a big WTFJS if we changed things such that  sometimes the syntax for a [[Call]] really means a [[Construct]].  Especially, if that meaning is dependent upon how the enclosing function was invoked rather than being statically determined.  Example, in the rationale document.

As I mentioned a couple of times in conversations, those of use who worked on developing this design found that the syntax really grows upon you.  It's big advantage is that is is unambiguously explicit.  If you want to invoke a constructor "as a constructor" you always say `new`. `new foo()` or new super(), it doesn't make a difference, `new` always means [[Constructor]]. If you want to invoke a constructor "as a function" you do a normal function invocation.  Again, either `foo()` or `super()`.  It's the same.  The [[Call]] syntax is never repurpose to mean [[Construct]] so there are no special cases to explain or remember.  Also, if you want to designate the `this` value used within a constructor you always explicit say `this=`, whether it is `this = super()' or `this=mayFactoy()` or `this = new Proxy(new super(), {...});`. Again, it is always explicit, no magic `super` calls that magically sets `this` as a side-effect.

It's this explicitness that grows on you.  The code actually says what it means.

Allen



More information about the es-discuss mailing list