@@new

# Jason Orendorff (7 years ago)

Allen asked me to fill out what @@new would mean. Here it is.

How new X works

new X(...args) ==== X[@@new](...args)

How it works for ES5 builtin constructors

Number(value) is specified in one section.

Number[@@new](value) is specified in another section.

To support subclassing, Number[@@new](), unlike ES5 1, would set the new object's prototype to this.prototype rather than Number.prototype. Otherwise it all works just like ES5.

The same goes for Object, Array, Function, Date, and the other builtins.

How it works for regular functions

Function.prototype[@@new](...arguments) is called. It works like this:

  1. Let proto = this.[[Get]]("prototype", this).

  2. If proto is not an object, throw a TypeError.

  3. Let obj = ObjectCreate(proto).

  4. If the this value or any object on its prototype chain is an ECMAScript language function (not a class), then

    a. Let F be that object.

    b. Let result = F.[[Call]](obj, arguments).

    c. If Type(result) is Object then return result.

  5. Return obj.

For regular functions, this behaves exactly like the [[Construct]] internal method in ES5 2. Step 4 is there to support regular functions and ES6 classes that subclass regular functions.

How it works for ES6 classes

The special constructor method in ClassDeclaration/ClassExpression syntax would desugar to a static @@new method. This class:

class Point {
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
}

would amount to this:

class Point {
    static [Symbol.new](x = 0, y = 0) {
        var obj = super[Symbol.new]();
        obj.x = x;
        obj.y = y;
        return obj;
    }
}

As in the current drafts, ES6 would have to do something mildly fancy (see 3 and step 8 of 4) to perform the desugaring.

If a class has no constructor method, it inherits the base class's static @@new method.

The "super Arguments" call syntax in the ES6 drafts would be constrained to appear only at the top of a constructor, as in Java:

class PowerUp {
    constructor(name, price) { ... }
}

class RocketPack extends PowerUp {
    constructor() {
        super("Rocket Pack", 1000);
        this.fuel = FULL_TANK;
    }
}

The RocketPack constructor would behave like this:

    static [Symbol.new]() {
        var obj = super[Symbol.new]("Rocket Pack", 1000);
        obj.fuel = FULL_TANK;
        return obj;
    }

This has different runtime semantics compared the super() syntax in the current ES6 draft, but it serves the same purpose.

And that's all.

Benefits

  • As with Allen's proposal, we would drop [[Construct]] and the construct trap.

  • Instances of builtin classes are never exposed to scripts before their internal slots are fully initialized.

  • @@create can be dropped entirely, but we retain the benefit to implementers that all Maps (for example) can have the same in-memory layout, and other objects can't become Maps at runtime.

  • Base class constructors are always called.

  • The ES6 draft language in 5 step 5, and many other similar places, would be backed out. ("If Type(O) is Object and O has a [[NumberData]] internal slot and the value of [[NumberData]] is undefined, then...")

  • The current ES6 draft specifies new builtin constructors (Map, Set, WeakMap) to throw a TypeError if called without new. This new convention could be reversed to what ES5 does with Function and Array: Map() would be the same as new Map(). User-defined classes could work this way too. I think developers would appreciate this.

  • These productions would be dropped from the ES6 grammar:

    MemberExpression : new super Arguments
    NewExpression : new super
    

    If for whatever reason users want these, they can call super[Symbol.new](). But I think they will no longer be needed.

    This would reduce the number of super forms to these 3:

    super.IdentifierName  // allowed in all methods
    super[Expression]     // allowed in all methods
    super(arguments)      // only allowed in constructors
    
# C. Scott Ananian (7 years ago)

+1.

I think you could also remove the 'construct' handler in Proxy (people.mozilla.org/~jorendorff/es6-draft.html#sec-construct-internal-method step 4), as the @@new method can be handled with the usual proxy method dispatch mechanism. I'll leave it to someone who better understands Proxies to give the details.

As I've mentioned before, I like the fact that this refactoring reduces the amount of magic syntax in the language; new X is now just sugar. The fact that it gets rid of the 'new super' syntax is even better (and demonstrates the virtue of shrinking the language in this way).

# Jasper St. Pierre (7 years ago)

On Tue, Jun 17, 2014 at 3:21 PM, Jason Orendorff <jason.orendorff at gmail.com>

wrote:

Allen asked me to fill out what @@new would mean. Here it is.

... snip ...

The "super Arguments" call syntax in the ES6 drafts would be constrained to appear only at the top of a constructor, as in Java:

class PowerUp {
    constructor(name, price) { ... }
}

class RocketPack extends PowerUp {
    constructor() {
        super("Rocket Pack", 1000);
        this.fuel = FULL_TANK;
    }
}

The RocketPack constructor would behave like this:

    static [Symbol.new]() {
        var obj = super[Symbol.new]("Rocket Pack", 1000);
        obj.fuel = FULL_TANK;
        return obj;
    }

How would

constructor() {
    if (rand() > 0.5)
        super("A");
}

behave? What about

constructor() {
    if (0)
        super();
}

?

This is a common trick in some languages that silently insert a super(); call if they didn't appear somewhere in the constructor to make sure that super() is always called.

We could prevent this behavior by making sure that super(); must be the first statement in a constructor, but that means that the subclass can't really influence the parent constructor execution at all.

The other option is to ask users to always chain up in their constructor, like they need to do in any other overridden method.

# Erik Arvidsson (7 years ago)

Remember that

class C {
  constructor() {}
}
assert(C === C.prototype.constructor);

What would C.prototype.constructor look like with your proposal? Is C === C[@@new]?

# Jason Orendorff (7 years ago)

On Tue, Jun 17, 2014 at 2:49 PM, Jasper St. Pierre <jstpierre at mecheye.net> wrote:

How would

constructor() {
    if (rand() > 0.5)
        super("A");
}

behave?

SyntaxError.

We could prevent this behavior by making sure that super(); must be the first statement in a constructor, [...]

That is what I proposed.

[...] but that means that the subclass can't really influence the parent constructor execution at all.

Any class can explicitly define a static [Symbol.new]() method, if desired.

Or the constructor method can return another object, so that the base class @@new method is called, but the object it created is thrown away. I should have mentioned that -- I would retain this behavior, which is already in ES6, but none of the examples used it so I forgot to say so.

Anyway --- skipping a base class constructor is not a normal thing to do. It shouldn't be the default.

# Jason Orendorff (7 years ago)

On Tue, Jun 17, 2014 at 2:55 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

What would C.prototype.constructor look like with your proposal?

ES6 already achieves C.prototype.constructor === C, for both functions and classes, simply by defining the two properties that way (in 1 steps 6 and 7). I wouldn't change that.

Is C === C[@@new]?

Good question. I think calling C(...args) should be the same as calling new C(...args). How best to specify that, I'm not sure.

I don't think C === C[@@new] needs to be a goal, since it wouldn't hold for regular functions, or for classes that don't define a constructor (those inherit their @@new method from the base class), or for classes that contain a static [Symbol.new]() method. But as an implementation detail, if that's the easiest way to specify it, OK.

-j

# Dmitry Soshnikov (7 years ago)

On Tue, Jun 17, 2014 at 1:02 PM, Jason Orendorff <jason.orendorff at gmail.com>

wrote:

On Tue, Jun 17, 2014 at 2:49 PM, Jasper St. Pierre <jstpierre at mecheye.net> wrote:

How would

constructor() {
    if (rand() > 0.5)
        super("A");
}

behave?

SyntaxError.

Ouch.. Don't know, there are several valid semantics. I don't know about Java (as you say it's enforces calling parent constructor automatically, and allows only on top of the child constructor), but there are other valid semantics (Python, Ruby, etc), that don't force this restriction.

Does it make implementation's simpler? I think the main driver should be developers life, not the implementation.

We could prevent this behavior by making sure that super(); must be the first statement in a constructor, [...]

That is what I proposed.

[...] but that means that the subclass can't really influence the parent constructor execution at all.

Any class can explicitly define a static [Symbol.new]() method, if desired.

Or the constructor method can return another object, so that the base class @@new method is called, but the object it created is thrown away. I should have mentioned that -- I would retain this behavior, which is already in ES6, but none of the examples used it so I forgot to say so.

Anyway --- skipping a base class constructor is not a normal thing to do. It shouldn't be the default.

That's the question. I guess there could valid cases when you wanna call parent constructor conditionally as was shown, or to call it after prologue initialization (which probably would set some default props needed for the parent ctor) in the child constructor.

Dmitry.

# Brian Terlson (7 years ago)

I like this proposal overall. Another benefit is that your super class constructors can return objects and things will just work (I think?).

I'm of the opinion that new should be required. Perhaps for class C { }, C is a function as if created by the expression function () { throw('Class must be instantiated with new'); }.

In addition to the super-must-be-first restriction, we might also want a 'cant reference this before super' restriction since this will not mean anything useful until after you super.

# Jason Orendorff (7 years ago)

On Tue, Jun 17, 2014 at 4:24 PM, Dmitry Soshnikov <dmitry.soshnikov at gmail.com> wrote:

Anyway --- skipping a base class constructor is not a normal thing to do. It shouldn't be the default.

That's the question. I guess there could valid cases when you wanna call parent constructor conditionally as was shown, or to call it after prologue initialization (which probably would set some default props needed for the parent ctor) in the child constructor.

That's fair. Developers who need that can write a static [Symbol.new]() method, of course.

I proposed what I did because I think it's more common to want to control the arguments passed to the base class constructor than to want to do other work before calling it, and it's very rare indeed to intentionally skip base class initialization. But all three are possible with this proposal.

# Kevin Smith (7 years ago)
constructor() {
    if (rand() > 0.5)
        super("A");
}

behave?

SyntaxError.

We could prevent this behavior by making sure that super(); must be the first statement in a constructor, [...]

That is what I proposed.

Sorry, but I can't get with this. The current class semantics works and I don't see a particularly compelling reason here to muck with it.

# Allen Wirfs-Brock (7 years ago)

On Jun 17, 2014, at 1:02 PM, Jason Orendorff wrote:

On Tue, Jun 17, 2014 at 2:49 PM, Jasper St. Pierre <jstpierre at mecheye.net> wrote:

How would

constructor() { if (rand() > 0.5) super("A"); }

behave?

SyntaxError.

We could prevent this behavior by making sure that super(); must be the first statement in a constructor, [...]

That is what I proposed.

We discussed issues like here early in the ES6 class design and the consensus at the time was that we didn't want to be restricted to a super first design.

Also, it's not clear what it really buys you because if you can say:

class RocketPack extends PowerUp { constructor() { super("Rocket Pack", 1000); this.fuel = FULL_TANK; } }

You can presumably also say:

class RocketPack extends PowerUp { constructor() { super(this.foo(), doSomethingWith(this)); this.fuel = FULL_TANK; } }

so you still have to deal with the issue of the meaning of 'this' before and after the super call in

The RocketPack constructor would behave like this:

   static [Symbol.new]() {
       var obj = super[Symbol.new]("Rocket Pack", 1000);
       obj.fuel = FULL_TANK;
       return obj;
   }

which reminds me that @@new is a class-side method, not an instance method. So, its natural 'this' value is the class object and and access to that 'this' value is important (for example, accessing the 'prototype' property in the default @@new, access class side constants and methods, etc.).

I guess, Jason is arguing that if you need to do any of these things, you need to explicitly code a @@new such as

class RocketPack extends PowerUp { static Symbol.new { let obj = super(this.foo(), doSomethingWith(this)); //this is normally RocketPack or a subclass of it obj.fuel = FULL_TANK; } }

BTW, it's not clear to me in this case what gets called for RocketPack();

Related, if an @@new method is explicitly provided in a class definition, is a constructor method definition forbidden?

# Brendan Eich (7 years ago)

So far, I prefer your proposal to draft ES6 by a lot -- especially since I missed the hideous Number special-casing spread around in the draft!

Jason Orendorff wrote:

Is C === C[@@new]?

Good question. I think calling C(...args) should be the same as calling new C(...args). How best to specify that, I'm not sure.

Isn't the question of whether C(...args) is allowed as short for new C(...args) a separable "option" from the rest of your proposal (and from draft ES6)?

I'd hate for your proposal to founder on this somewhat controversial issue.

I don't think C === C[@@new] needs to be a goal,

Should be specified not to be === in my view because:

it wouldn't hold for regular functions, or for classes that don't define a constructor (those inherit their @@new method from the base class), or for classes that contain a static [Symbol.new]() method.

See Allen's latest followup on this -- is it a static error to have both constructor and the static Symbol.new method?

But as an implementation detail, if that's the easiest way to specify it, OK.

See above. LMK if you disagree with anything, or anything's unclear. Thanks again for writing up your counter-proposal!

# Brian Terlson (7 years ago)

I realized I'm not sure how my ClassMix library will work with this proposal, if at all. Clearly the subclass constructor in the example would have to change to account for the fact that super must be called, along with the default constructor created by the library, but I'm not sure how. Seems like I'd have to invoke super constructors and explicitly mix the instances returned. At any rate this code is definitely much harder to implement with the @@new proposal.

# Erik Arvidsson (7 years ago)

On Jun 17, 2014 7:44 PM, "Brendan Eich" <brendan at mozilla.org> wrote:

So far, I prefer your proposal to draft ES6 by a lot -- especially since

I missed the hideous Number special-casing spread around in the draft!

I don't.

How does this work with legacy classes?

function B() { this.x = 1; } class C extends B {}

I just do not see how this solves any issues. As far as I can tell we will still need to call the constructor after @@new is called. We need the two phase initialization. One function for creating the instance and one to initialize it.

Jason Orendorff wrote:

Is C === C[@@new]?

Good question. I think calling C(...args) should be the same as calling new C(...args). How best to specify that, I'm not sure.

Isn't the question of whether C(...args) is allowed as short for new

C(...args) a separable "option" from the rest of your proposal (and from draft ES6)?

I'd hate for your proposal to founder on this somewhat controversial

issue.

I don't think C === C[@@new] needs to be a goal,

Should be specified not to be === in my view because:

it wouldn't hold for regular functions, or for classes that don't define a constructor (those inherit their @@new method from the base class), or for classes that contain a static [Symbol.new]() method.

See Allen's latest followup on this -- is it a static error to have both

constructor and the static Symbol.new method?

But as an implementation detail, if that's the easiest way to specify it, OK.

See above. LMK if you disagree with anything, or anything's unclear.

Thanks again for writing up your counter-proposal!

# Jason Orendorff (7 years ago)

On Tue, Jun 17, 2014 at 6:55 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

How does this work with legacy classes?

function B() { this.x = 1; } class C extends B {}

That works!

new C desugars to C[@@new](). C doesn't have a @@new method of its own, so it inherits Function.prototype[@@new].

The algorithm for that method is given in the proposal. Step 1 selects the right prototype here (C.prototype), and step 4 calls B as desired.

# Domenic Denicola (7 years ago)

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Brendan Eich

See Allen's latest followup on this -- is it a static error to have both constructor and the static Symbol.new method?

IMO it shouldn't be, because it'd be weird to get an error for constructor + static [Symbol.new](), but to not get an error for constructor + static [Symbol["n" + "ew"]]() and similar.

Another way of guiding the decision: I don't quite recall where the spec landed { x: 1, ["x"]: 2 }, but we should probably be consistent with that.

# Allen Wirfs-Brock (7 years ago)

On Jun 17, 2014, at 4:44 PM, Brendan Eich wrote:

... especially since I missed the hideous Number special-casing spread around in the draft!

could you be a bit more specific about what you are referring to?

# Jasper St. Pierre (7 years ago)

On Tue, Jun 17, 2014 at 4:02 PM, Jason Orendorff <jason.orendorff at gmail.com>

wrote:

On Tue, Jun 17, 2014 at 2:49 PM, Jasper St. Pierre <jstpierre at mecheye.net> wrote:

How would

constructor() {
    if (rand() > 0.5)
        super("A");
}

behave?

SyntaxError.

We could prevent this behavior by making sure that super(); must be the first statement in a constructor, [...]

That is what I proposed.

What does:

class B {
    constructor(a) {
        this._a = a;
    }
}

class C extends B {
    constructor(b) {
        this._b = b;
    }
}

do? Some kind of compiler error?

# Allen Wirfs-Brock (7 years ago)

On Jun 17, 2014, at 5:22 PM, Allen Wirfs-Brock wrote:

On Jun 17, 2014, at 4:44 PM, Brendan Eich wrote:

... especially since I missed the hideous Number special-casing spread around in the draft!

could you be a bit more specific about what you are referring to?

Never mind, I think I know what you mean. You mean, the already initialized checks which fall out of objects having distinct allocation and initialization functions. Those checks only occur in functions that directly access private internal state of built-in oibjects.

Such methods typically have a prologue that look something like this:

• 1.	Let M be the this value.
• 2.	If Type(M) is not Object, then throw a TypeError exception.
• 3.	If M does not have a [[MapData]] internal slot throw a TypeError exception.
• 4.	If M’s [[MapData]] internal slot is undefined, then throw a TypeError exception.

The fourth step could probably be eliminated under Jason's proposal, the other would still be needed. One of the things we need to explore is whether we loose would loose any important subclass flexibility by tightly couple allocation and initialization again. The 'super' must be first issue is at the forefront of that question.

# Kevin Smith (7 years ago)

SyntaxError.

We could prevent this behavior by making sure that super(); must be the first statement in a constructor, [...]

That is what I proposed.

Sorry for my previous gut reaction. This looks like a good proposal, but it makes a break with the evolutionary design of ES classes. I think sticking to an evolutionary path with classes has played a big part in their success and I think we should stick to that.

Furthermore, when we were hashing out classes, several people (including me I think) tried to suggest ways to maintain the C() <=> new C()

equivalence established by the built-ins, and it never really took off. I think maybe the reason is that that equivalence isn't very useful.

# Boris Zbarsky (7 years ago)

On 6/17/14, 8:47 PM, Allen Wirfs-Brock wrote:

You mean, the already initialized checks which fall out of objects having distinct allocation and initialization functions. Those checks only occur in functions that directly access private internal state of built-in oibjects.

I should note that for typical "DOM" (not just nodes; pretty much every non-ES-builtin in the web platform) this is every single function. And that the current separation of allocation and initialization means that for these objects we have the following choices (probably on a per-object-class basis):

  1. Make the object not subclassable.
  2. Make the object subclassable and have WebIDL this value handling perform some sort of "is initialized" checks.
  3. Make the object subclassable and have specification prose for all its methods/properties perform "is initialized" checks.

.#1 is generally undersirable, #3 requires a level of understanding of ES guts on the part of web spec authors that I suspect is not present and might be hard to achieve (and in particular requires them to explicitly opt in to making things subclassable).

Even #2 is a bit of a pain in terms of requiring extra work on every single thing you do, which is not all that great from a performance standpoint.

If we can have a world in which web platform objects don't exist in constructed-but-not-initialized states, I personally would much prefer that....

# Brendan Eich (7 years ago)

Domenic Denicola wrote:

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Brendan Eich

See Allen's latest followup on this -- is it a static error to have both constructor and the static Symbol.new method?

IMO it shouldn't be, because it'd be weird to get an error for constructor + static [Symbol.new](), but to not get an error for constructor + static [Symbol["n" + "ew"]]() and similar.

Silly me -- strike that "static" before "error" -- it still could be a strict error, but see latest meeting minutes where MarkM changed his position (cited below).

Another way of guiding the decision: I don't quite recall where the spec landed { x: 1, ["x"]: 2 }, but we should probably be consistent with that.

Mark Miller: I am ok with removing the constraint that duplicate dynamic object properties throw (in strict mode) with the caveat that we also remove the same constraint for duplicate static properties.

from esdiscuss.org/notes/2014-06-06#rest-properties-and-spread-properties-sebastian-markb-ge-.

Still, in this case we have a ClassElement special form, constructor(){}. This distinction adds a choice not present in the ObjectLiteral case: to have a strict (dynamic) error on duplicate Symbol.new-equivalent name.

# Brendan Eich (7 years ago)

Boris Zbarsky wrote:

If we can have a world in which web platform objects don't exist in constructed-but-not-initialized states, I personally would much prefer that....

# Brendan Eich (7 years ago)

Kevin Smith wrote:

Sorry for my previous gut reaction. This looks like a good proposal, but it makes a break with the evolutionary design of ES classes.

How so? @@create or @@new, either way something in ES6 is new that was not in classes-desugared-to-functions.

Furthermore, when we were hashing out classes, several people (including me I think) tried to suggest ways to maintain the C() <=> new C() equivalence established by the built-ins, and it never really took off. I think maybe the reason is that that equivalence isn't very useful.

I think Jason's proposal leaves this C ==== C[Symbol.new] condition a separate choice from everything else, and choosing the way you prefer does not break the rest of the design. Jason?

# Jason Orendorff (7 years ago)

On Tue, Jun 17, 2014 at 11:11 PM, Brendan Eich <brendan at mozilla.org> wrote:

I think Jason's proposal leaves this C ==== C[Symbol.new] condition a separate choice from everything else, and choosing the way you prefer does not break the rest of the design. Jason?

Yes, that's correct.

# Tom Van Cutsem (7 years ago)

I really like this proposal.

2014-06-17 21:32 GMT+02:00 C. Scott Ananian <ecmascript at cscott.net>:

+1.

I think you could also remove the 'construct' handler in Proxy ( people.mozilla.org/~jorendorff/es6-draft.html#sec-construct-internal-method step 4), as the @@new method can be handled with the usual proxy method dispatch mechanism. I'll leave it to someone who better understands Proxies to give the details.

That's what Jason meant by getting rid of the construct trap.

# David Herman (7 years ago)

Your proposal is appealing but I haven't convinced myself it works. There are a couple bits I haven't quite grokked.

The special constructor method in ClassDeclaration/ClassExpression syntax would desugar to a static @@new method. This class:

class Point { constructor(x = 0, y = 0) { this.x = x; this.y = y; } }

would amount to this:

class Point { static [Symbol.new](x = 0, y = 0) { var obj = superSymbol.new; obj.x = x; obj.y = y; return obj; } }

You're inlining the method body into the @@new body, but I want to make sure I understand what the specification of the Point function itself would be. You've said Point !== Point[@@new] but calling Point(...args) should behave the same as new Point(...args). So then would the specification of the Point function's behavior just be to delegate to Point@@new?

The "super Arguments" call syntax in the ES6 drafts would be constrained to appear only at the top of a constructor, as in Java:

This part is what I'm the most unclear about. What invariant are you trying to maintain? It seems like you're using this to attempt to guarantee that all superclasses have had the chance to initialize their internal fields before user code starts running, so that built-in classes can have consistent internal representations and not have to worry about guarding against reading from uninitialized internal slots. But this is a tricky invariant to guarantee. First of all I think you'd have to put syntactic restrictions in place to disallow referring to this in the super call, to avoid e.g.:

class Sneaky extends Date { constructor(x) { super((console.log(this.valueOf()), x)); } }

But that's not enough, because you also have to worry about the superclass constructors calling overloaded methods:

class Dupe extends Date { constructor(x) { super(x); initDupe(); } initDupe() { ... } }

class Sneaky extends Dupe { constructor(x) { super(x); } initDupe() { console.log(this.valueOf()); super.initDupe(); } }

(These are common pitfalls in languages that try to provide hard guarantees about field initialization. They come up, for example, in typed OO languages that try to provide guaranteed non-nullable fields.)

But maybe I'm misunderstanding what invariant you're aiming at?

  • Base class constructors are always called.

Are you saying you would not only restrict the super call to the top, but require one to always be there? Or would an absence of a super call imply an implicit super()?

  • These productions would be dropped from the ES6 grammar:

    MemberExpression : new super Arguments NewExpression : new super

Clearly with your design super[Symbol.new](...args) is equivalent to new super. Is it also emulate-able in the @@create semantics also? If so it seems like the question of whether to keep or drop the new super syntax is 100% orthogonal to your proposal.

# Dmitry Soshnikov (7 years ago)

On Tue, Jun 17, 2014 at 12:21 PM, Jason Orendorff <jason.orendorff at gmail.com

wrote: ...

Benefits

  • As with Allen's proposal, we would drop [[Construct]] and the construct trap.

  • @@create can be dropped entirely, but we retain the benefit to implementers that all Maps (for example) can have the same in-memory layout, and other objects can't become Maps at runtime.

These are pure implementation details, and only implementation improvements, that are not that interesting to actual developers.

This would reduce the number of `super` forms to these 3:

    super.IdentifierName  // allowed in all methods
    super[Expression]     // allowed in all methods
    super(arguments)      // only allowed in constructors

I missed that: so we got another restriction, and devs won't be able to use convenient concise super() calls from other methods? I don't think it's the best direction.

Overall, we got three restrictions:

(1) super only on top of the constructor, (2) want it or not: parent constructor is always called, (3) you can't use concise super calls from methods.

  • bonus (4) you have to use some verbose syntax of static Symbol.new {} to do needed hacks.

What all these for? To exclude [[Construct]]/@@create form the implementation? But developers are not interested in the implementation. Developers want to make convenient super-calls, do not call parent constructors if it's not needed (yes, it's rare, but can be), or to call it whenever they want after predefined prologue.

Personally I think two traps are more intuitive and convenient (and are present in many other languages: C++, Ruby, etc) than this "spec/implementation-optimization", and users would appreciate the new and constructor traps. The former can be used in rare cases when they'll need custom allocation (and this value is not available there yet), and the constructor is a normal initialiser of the allocated instance.

I mean, there should be a very strong reason to change an intuitive semantics to this approach. "Spec/implementation optimizations" is not the best reason, and that "parent constructor should always be called" is a subjective, and just one of the semantics.

P.S.: as for the libraries, some "libraries" (actually my tests) were also coupled to this allocate/initialize pair hooks approach: gist.github.com/DmitrySoshnikov/1367025#file-r-proto-class-class-js-L23-L41

Dmitry

# Andreas Rossberg (7 years ago)

On 18 June 2014 09:40, Dmitry Soshnikov <dmitry.soshnikov at gmail.com> wrote:

On Tue, Jun 17, 2014 at 12:21 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote:

Benefits

  • As with Allen's proposal, we would drop [[Construct]] and the construct trap.

  • @@create can be dropped entirely, but we retain the benefit to implementers that all Maps (for example) can have the same in-memory layout, and other objects can't become Maps at runtime.

These are pure implementation details, and only implementation improvements, that are not that interesting to actual developers.

The main part of this point is getting rid of @@create, which is far more than an implementation detail. If we can actually pull this off -- in particular, getting rid of the ability to observe half-constructed objects -- then I'd +100 it. It's still worth trying, but as David points out, it's not so easy.

# Boris Zbarsky (7 years ago)

On 6/18/14, 3:40 AM, Dmitry Soshnikov wrote:

Personally I think two traps are more intuitive and convenient (and are present in many other languages: C++, Ruby, etc)

C++ has the sorts of restrictions on calling the superclass constructor (have to do it up front, can't skip doing it) that the @@new proposal has, no?

and that "parent constructor should always be called" is a subjective, and just one of the semantics.

While true, I'd like to see a concrete proposal about how we actually proceed with making the web platform subclassable in the @@create world. Whereas in the @@new world subclassibility by default is at least somewhat viable...

# Domenic Denicola (7 years ago)

From: Jason Orendorff<mailto:jason.orendorff at gmail.com>

Sent: ‎2014-‎06-‎17 15:22 To: Allen Wirfs-Brock<mailto:allen at wirfs-brock.com>

Cc: EcmaScript<mailto:es-discuss at mozilla.org>; Mark Miller<mailto:erights at gmail.com>

Subject: @@new

Allen asked me to fill out what @@new would mean. Here it is.

How new X works

new X(...args) ==== X[@@new](...args)

How it works for ES5 builtin constructors

Number(value) is specified in one section.

Number[@@new](value) is specified in another section.

To support subclassing, Number[@@new](), unlike ES5 1, would set the new object's prototype to this.prototype rather than Number.prototype. Otherwise it all works just like ES5.

The same goes for Object, Array, Function, Date, and the other builtins.

How it works for regular functions

Function.prototype[@@new](...arguments) is called. It works like this:

  1. Let proto = this.[[Get]]("prototype", this).

  2. If proto is not an object, throw a TypeError.

  3. Let obj = ObjectCreate(proto).

  4. If the this value or any object on its prototype chain is an ECMAScript language function (not a class), then

    a. Let F be that object.

    b. Let result = F.[[Call]](obj, arguments).

    c. If Type(result) is Object then return result.

  5. Return obj.

For regular functions, this behaves exactly like the [[Construct]] internal method in ES5 2. Step 4 is there to support regular functions and ES6 classes that subclass regular functions.

How it works for ES6 classes

The special constructor method in ClassDeclaration/ClassExpression syntax would desugar to a static @@new method. This class:

class Point {
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
}

would amount to this:

class Point {
    static [Symbol.new](x = 0, y = 0) {
        var obj = super[Symbol.new]();
        obj.x = x;
        obj.y = y;
        return obj;
    }
}

As in the current drafts, ES6 would have to do something mildly fancy (see 3 and step 8 of 4) to perform the desugaring.

If a class has no constructor method, it inherits the base class's static @@new method.

The "super Arguments" call syntax in the ES6 drafts would be constrained to appear only at the top of a constructor, as in Java:

class PowerUp {
    constructor(name, price) { ... }
}

class RocketPack extends PowerUp {
    constructor() {
        super("Rocket Pack", 1000);
        this.fuel = FULL_TANK;
    }
}

The RocketPack constructor would behave like this:

    static [Symbol.new]() {
        var obj = super[Symbol.new]("Rocket Pack", 1000);
        obj.fuel = FULL_TANK;
        return obj;
    }

This has different runtime semantics compared the super() syntax in the current ES6 draft, but it serves the same purpose.

And that's all.

Benefits

  • As with Allen's proposal, we would drop [[Construct]] and the construct trap.

  • Instances of builtin classes are never exposed to scripts before their internal slots are fully initialized.

  • @@create can be dropped entirely, but we retain the benefit to implementers that all Maps (for example) can have the same in-memory layout, and other objects can't become Maps at runtime.

  • Base class constructors are always called.

  • The ES6 draft language in 5 step 5, and many other similar places, would be backed out. ("If Type(O) is Object and O has a [[NumberData]] internal slot and the value of [[NumberData]] is undefined, then...")

  • The current ES6 draft specifies new builtin constructors (Map, Set, WeakMap) to throw a TypeError if called without new. This new convention could be reversed to what ES5 does with Function and Array: Map() would be the same as new Map(). User-defined classes could work this way too. I think developers would appreciate this.

  • These productions would be dropped from the ES6 grammar:

    MemberExpression : new super Arguments
    NewExpression : new super
    

    If for whatever reason users want these, they can call super[Symbol.new](). But I think they will no longer be needed.

    This would reduce the number of super forms to these 3:

    super.IdentifierName  // allowed in all methods
    super[Expression]     // allowed in all methods
    super(arguments)      // only allowed in constructors
    
# Domenic Denicola (7 years ago)

What happens if I put [Symbol.new] on a non-function object? Is it now constructable? Presumably still not callable though, right?

# Erik Arvidsson (7 years ago)

What about the other direction?

class B { constructor(x) { this.x = x; } static Symbol.create { var o = super(); weakMap.set(o, 12345678); // DOM wrapper foo return o; } }

function C(x) { B.call(this, x); } C.proto = B; C.prototype = {proto: B.prototype};

I think the most concerning part of this proposal is that constructor(...) gets replaced by static [Symbol.new](...) with strange semantics regarding this. If we instead had @@new call constructor by default I think most of these concerns go away but then again we are back to two initialization functions and the possibility to observe an object that never went through its constructor.

# Erik Arvidsson (7 years ago)

Another thing to consider is to ensure that @@create initializes all of its slots. For example Date@@create could set its [[DateValue]] to NaN, Map@@create could set its [[MapData]] to an empty list and so on.

This also fits how @@create works for DOM, where the creation of the instance would set up the internal DOM wrapper pointer, never exposing a non initialized DOM object to user code.

# Domenic Denicola (7 years ago)

From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Boris Zbarsky <bzbarsky at MIT.EDU>

While true, I'd like to see a concrete proposal about how we actually proceed with making the web platform subclassable in the @@create world. Whereas in the @@new world subclassibility by default is at least somewhat viable...

Hmm, I thought we'd already established how this would work. It's pretty seamless with the @@create approach. See:

gist.github.com/domenic/3ce8f57a33a25473f508

The "domPointer" stuff is kind of handwavey, but it's still pretty clear how it would work.


Following up on Arv's point about the DOM's use of @@create, my understanding is that having the two-phase initialization is quite important for the DOM, e.g. elements created by the parser will be allocated but never initialized (their constructors will never run). You can see this in action today with e.g. HTMLImageElement.

# Kevin Smith (7 years ago)

Another thing to consider is to ensure that @@create initializes all of its slots. For example Date@@create could set its [[DateValue]] to NaN, Map@@create could set its [[MapData]] to an empty list and so on.

I think if you go that route you still may need some checks in place to ensure that the initializer (constructor) is only executed once.

For instance, you'd never want the Promise constructor to be called twice on some promise object.

# Jason Orendorff (7 years ago)

On Wed, Jun 18, 2014 at 8:55 AM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

What happens if I put [Symbol.new] on a non-function object? Is it now constructable? Presumably still not callable though, right?

Yes, it would be constructable but still not callable.

# Boris Zbarsky (7 years ago)

On 6/18/14, 11:39 AM, Erik Arvidsson wrote:

This also fits how @@create works for DOM, where the creation of the instance would set up the internal DOM wrapper pointer, never exposing a non initialized DOM object to user code.

Note that in that setup it's impossible to introduce an HTMLElement constructor like this:

var el = new HTMLElement("iframe");

because the @@create doesn't have the tag name available to create the right sort of object...

This is the flyweight object issue that was mentioned up-thread too: if you have to examine your arguments to decide what sort of object to create, then @@create is not a viable option.

Whether we need to make this sort of constructor possible is a separate issue, of course; for newer specs we're using separate interfaces for separate things, so don't need this sort of constructor, I think.

# Boris Zbarsky (7 years ago)

On 6/18/14, 12:09 PM, Domenic Denicola wrote:

From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Boris Zbarsky <bzbarsky at MIT.EDU>

While true, I'd like to see a concrete proposal about how we actually proceed with making the web platform subclassable in the @@create world. Whereas in the @@new world subclassibility by default is at least somewhat viable...

Hmm, I thought we'd already established how this would work. It's pretty seamless with the @@create approach. See:

gist.github.com/domenic/3ce8f57a33a25473f508

The "domPointer" stuff is kind of handwavey, but it's still pretty clear how it would work.

So this is basically option 2 from esdiscuss/2014-June/037849 if I understand right?

# Allen Wirfs-Brock (7 years ago)

On Jun 18, 2014, at 8:33 AM, Erik Arvidsson wrote:

I think the most concerning part of this proposal is that constructor(...) gets replaced by static [Symbol.new](...) with strange semantics regarding this. If we instead had @@new call constructor by default I think most of these concerns go away but then again we are back to two initialization functions and the possibility to observe an object that never went through its constructor.

This is also one of my bigger concerns. I think the rewriting and reinterpretation of what the use wrote as a a constructor may be very problematic. For example,how does this get translated:

class extends C { constructor() { super(); super.foo(); //apply the inherited foo method to the new object
} }

If we had a design that didn't require the rewriting of the user provided constructor I'd be much more comfortable. Thinking...more latter...

# Allen Wirfs-Brock (7 years ago)

On Jun 18, 2014, at 8:39 AM, Erik Arvidsson wrote:

Another thing to consider is to ensure that @@create initializes all of its slots. For example Date@@create could set its [[DateValue]] to NaN, Map@@create could set its [[MapData]] to an empty list and so on.

This also fits how @@create works for DOM, where the creation of the instance would set up the internal DOM wrapper pointer, never exposing a non initialized DOM object to user code.

Right, that's the line I'm exploring. If it can work out @@create are @@new more or less the same thing, except for the constructor rewriting.

# Domenic Denicola (7 years ago)

From: Boris Zbarsky [mailto:bzbarsky at MIT.EDU]

So this is basically option 2 from

esdiscuss/2014-June/037849 if I understand right?

Yes, although in this case no initialization checks are necessary, just brand checks. Which I guess speaks to your point about how getting this right is tricky.

I am not sure there are too many cases where there's an interesting distinction between allocated and initialized for the DOM, though. Given how few DOM objects have reasonable constructors to begin with, it seems like most of their work is being done (conceptually) in Symbol.create anyway.

# Erik Arvidsson (7 years ago)

On Wed, Jun 18, 2014 at 1:07 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

On 6/18/14, 11:39 AM, Erik Arvidsson wrote:

This also fits how @@create works for DOM, where the creation of the instance would set up the internal DOM wrapper pointer, never exposing a non initialized DOM object to user code.

Note that in that setup it's impossible to introduce an HTMLElement constructor like this:

var el = new HTMLElement("iframe");

This particular one can be done because if the constructor returns an object that is used instead.

function HTMLElement(tagName = undefined) { if (tagName === undefined) throw ... var ctor = lookupConstructor(tagName); return new ctor; }

because the @@create doesn't have the tag name available to create the right sort of object...

But the constructor is able to return any object it want, basically making the constructor into a factory method.

This is an ugly pattern so it is discouraged but it does give you some leeway when you need to support strange things.

# Brendan Eich (7 years ago)

Allen Wirfs-Brock wrote:

This is also one of my bigger concerns. I think the rewriting and reinterpretation of what the use wrote as a a constructor may be very problematic. For example,how does this get translated:

class extends C { constructor() { super(); super.foo(); //apply the inherited foo method to the new object } }

If we had a design that didn't require the rewriting of the user provided constructor I'd be much more comfortable. Thinking...more latter...

I used to think this way, but dherman pointed out years ago that constructor in ES6 class is a special form, even though it looks like a method. Here's an es-discuss thread from three years ago:

esdiscuss.org/topic/why

# Brendan Eich (7 years ago)

Domenic Denicola wrote:

I am not sure there are too many cases where there's an interesting distinction between allocated and initialized for the DOM, though. Given how few DOM objects have reasonable constructors to begin with, it seems like most of their work is being done (conceptually) in Symbol.create anyway.

This all looks at the past, where the DOM is warty as hell (I can say that, I started it). What about the future?

# Jason Orendorff (7 years ago)

On Wed, Jun 18, 2014 at 1:40 AM, David Herman <dherman at mozilla.com> wrote:

You're inlining the method body into the @@new body, but I want to make sure I understand what the specification of the Point function itself would be. You've said Point !== Point[@@new] but calling Point(...args) should behave the same as new Point(...args). So then would the specification of the Point function's behavior just be to delegate to Point@@new?

Yes, but that's not central to the proposal.

As it stands in the current draft, Point() would throw a TypeError, because it tries to set this.x = x and this would be undefined. We could keep the semantics exactly as they are even with @@new: just run the body of the constructor function... It wouldn't be useful though, just weird. I dunno, the status quo is weird. I don't feel strongly about this.

The "super Arguments" call syntax in the ES6 drafts would be constrained to appear only at the top of a constructor, as in Java:

This part is what I'm the most unclear about. What invariant are you trying to maintain? It seems like you're using this to attempt to guarantee that all superclasses have had the chance to initialize their internal fields before user code starts running, [...] But maybe I'm misunderstanding what invariant you're aiming at?

Yeah. I don't care about the new method itself leaking this. I just want to make sure it's possible for a class implemented with reasonable care to initialize its instances fully before exposing them---and prove it, by making the ES6 builtin classes do so.

I had a syntactic restriction on super for two reasons, neither one related to the kind of invariant you're thinking of: (1) if the super() call isn't there, I wanted to call it implicitly, for convenience and correctness-by-default; and, (2) until you call the base class @@new, there is no this value. But the syntactic restriction isn't sufficient for purpose #2, and besides none of us like it, so I will try to find another way. :)

  • Base class constructors are always called.

Are you saying you would not only restrict the super call to the top, but require one to always be there? Or would an absence of a super call imply an implicit super()?

The latter.

  • These productions would be dropped from the ES6 grammar:

    MemberExpression : new super Arguments NewExpression : new super

Clearly with your design super[Symbol.new](...args) is equivalent to new super. Is it also emulate-able in the @@create semantics also?

I think yes, if we drop [[Construct]] as Allen separately proposed. As long as [[Construct]] exists as a separate internal method, new super is inconvenient to simulate.

If so it seems like the question of whether to keep or drop the new super syntax is 100% orthogonal to your proposal.

I think that's right. I assumed the use cases for new super must be something to do with how objects are constructed now, but on reflection I don't know what the use cases are.

# Allen Wirfs-Brock (7 years ago)

On Jun 18, 2014, at 11:09 AM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

This is also one of my bigger concerns. I think the rewriting and reinterpretation of what the use wrote as a a constructor may be very problematic. For example,how does this get translated:

class extends C { constructor() { super(); super.foo(); //apply the inherited foo method to the new object } }

If we had a design that didn't require the rewriting of the user provided constructor I'd be much more comfortable. Thinking...more latter...

I used to think this way, but dherman pointed out years ago that constructor in ES6 class is a special form, even though it looks like a method. Here's an es-discuss thread from three years ago:

esdiscuss.org/topic/why-not-new-instead-of-constructor

Except in the current design there is no special reinterpretation of the constructor method body semantics. The constructor method definition simply provides body of the constructor function and semantically is exactly the same as the body that is provided in a Function definition. See people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation and in particular step 10 which calls people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-definemethod which just calls people.mozilla.org/~jorendorff/es6-draft.html#sec-functioncreate . No special processing of the body anywhere along that path. The only special treatment prior to step 10 is about choosing a default constructor body if a constructor method isn't provided in the class declaration.

It is an alternative syntax for providing a the body of a function, but it has no unique semantics. That seems significant to me.

# C. Scott Ananian (7 years ago)

On Wed, Jun 18, 2014 at 2:18 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote:

I had a syntactic restriction on super for two reasons, neither one related to the kind of invariant you're thinking of: (1) if the super() call isn't there, I wanted to call it implicitly, for convenience and correctness-by-default; and, (2) until you call the base class @@new, there is no this value. But the syntactic restriction isn't sufficient for purpose #2, and besides none of us like it, so I will try to find another way. :)

Just for the record, Java maintains a similar syntactic/semantic invariant on the use of super, so it can certainly be made to work. On the other hand, it's also entirely true that nobody likes it very much in Java, although they live can live with it (and there are workarounds, like making one of the arguments a call to a static method to do important work before the superclass constructor is invoked).

# Allen Wirfs-Brock (7 years ago)

On Jun 18, 2014, at 11:18 AM, Jason Orendorff wrote:

If so it seems like the question of whether to keep or drop the new super syntax is 100% orthogonal to your proposal.

I think that's right. I assumed the use cases for new super must be something to do with how objects are constructed now, but on reflection I don't know what the use cases are.

It's there now mostly just for syntactic consistency. You can say 'new this' so why wouldn't you be able to say 'new super'. It probably has limited use cases but it is one of those things that falls out of the overall expression syntax and I think if somebody does have a use case it would be surprising that it isn't allowed.

# Brendan Eich (7 years ago)

Allen Wirfs-Brock wrote:

Except in the current design there is no special reinterpretation of the constructor method body semantics. The constructor method definition simply provides body of the constructor function and semantically is exactly the same as the body that is provided in a Function definition. See people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation, people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation and in particular step 10 which calls people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-definemethod, people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-definemethod which just calls people.mozilla.org/~jorendorff/es6-draft.html#sec-functioncreate, people.mozilla.org/~jorendorff/es6-draft.html#sec-functioncreate . No special processing of the body anywhere along that path. The only special treatment prior to step 10 is about choosing a default constructor body if a constructor method isn't provided in the class declaration.

Fair point, things have changed. Still, here are some other bits of magic that make constructor(){} as ClassElement not just a method definition. Here's another:

  • 'constructor' is not enumerable but methods are.

It is an alternative syntax for providing a the body of a function, but it has no unique semantics. That seems significant to me.

Not so much to me, and one could argue for static @@new{} sugar similarly.

The most important thing here (I agree with Andreas R.) is -- if possible -- avoiding uninitialized object observability.

# Brendan Eich (7 years ago)

Brendan Eich wrote:

Still, here

Er, "there"

are some other bits of magic that make constructor(){} as ClassElement not just a method definition. Here's another:

  • 'constructor' is not enumerable but methods are.

Someone should compile the complete list.

# Domenic Denicola (7 years ago)

From: Brendan Eich <brendan at mozilla.org>

This all looks at the past, where the DOM is warty as hell (I can say that, I started it). What about the future?

Much the same, from what I understand. The parser needs to be able to create elements, including custom elements, without knowing what their constructor signature is. As specced 1, registering the element will set its @@create value to something that creates the element in this way, and presumably the parser will be updated to invoke @@create. (This is the same as how, in ES5, the parser invokes the createdCallback property passed to registerElement.)

Dmitri, did I get that right?

# Erik Arvidsson (7 years ago)

On Wed, Jun 18, 2014 at 2:54 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:

From: Brendan Eich <brendan at mozilla.org>

This all looks at the past, where the DOM is warty as hell (I can say that, I started it). What about the future?

Much the same, from what I understand. The parser needs to be able to create elements, including custom elements, without knowing what their constructor signature is. As specced [1], registering the element will set its @@create value to something that creates the element in this way, and presumably the parser will be updated to invoke @@create. (This is the same as how, in ES5, the parser invokes the createdCallback property passed to registerElement.)

Dmitri, did I get that right?

Channeling Dimitri here since I designed that part...

The important part for Custom Elements is that @@create is non configurable, non writable so that we can use a native implementation that sets up all the internal wrapper pointers and whatnot. This is so that the parser does not have to call into user code at parse time.

However, this still allows user code to provide a constructor that gets invoked when doing new MyCustomElement(x, y, z).

# Boris Zbarsky (7 years ago)

On 6/18/14, 1:46 PM, Domenic Denicola wrote:

Yes, although in this case no initialization checks are necessary, just brand checks.

Well. That depends on the desired behavior.

For example, say you have a DOMPoint as in your example above that is supposed to return Numbers for x and y.

In your pseudocode, what happens if .x or .y is accessed after @@create but before the constructor runs? That depends on what slot initialization, if any, allocateNativeObjectWithSlots performs. If a spec author wanted to not depend on implementation details like that, they would need to define that @@create puts something in the slots separate from whatever work the constructor does.

Note that at this point we're already forcing spec authors to think about the exact @@create behavior to make things subclassable and that in particular, every single existing spec that has a constructor will need to be revisited and modified as needed.

Doing an initialization check instead of a brand check would avoid needing to define @@create behavior in that sort of detail, I think, but adds extra checks that need to be performed, in addition to the brand check, and extra state (initialized or not) to be stored.

Given how few DOM objects have reasonable constructors to begin with

I'm not sure how you're defining "reasonable", but a quick look at Gecko's IDL shows 136 IDL files that have at least constructor in them that takes some arguments and does not simply take a single optional argument (a dictionary, say). For comparison, we have 586 IDL files total...

Some of these are not standards yet, and a few I think have a no-argument constructor in addition to the one with arguments, and a bunch are event constructors that would all presumably be defined in a similar way in the spec, but I think you're significantly underestimating the number of things that have constructors that actually do something with their arguments.

# Boris Zbarsky (7 years ago)

On 6/18/14, 2:54 PM, Domenic Denicola wrote:

Much the same, from what I understand. The parser needs to be able to create elements, including custom elements

Most "DOM" objects being added nowadays are not elements at all. We need a less ambiguous name for "web platform things". I've sometimes called them "IDL objects" or "Web IDL objects", but that's not all that clear either..

# Boris Zbarsky (7 years ago)

On 6/18/14, 1:58 PM, Erik Arvidsson wrote:

This particular one can be done because if the constructor returns an object that is used instead.

Ah, I see.

This is an ugly pattern so it is discouraged

Especially given that it requires special care when subclassing somethin with such a constructor, correct?

# Allen Wirfs-Brock (7 years ago)

On Jun 18, 2014, at 11:41 AM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

Except in the current design there is no special reinterpretation of the constructor method body semantics. The constructor method definition simply provides body of the constructor function and semantically is exactly the same as the body that is provided in a Function definition. See people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation, people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation and in particular step 10 which calls people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-definemethod, people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-definemethod which just calls people.mozilla.org/~jorendorff/es6-draft.html#sec-functioncreate, people.mozilla.org/~jorendorff/es6-draft.html#sec-functioncreate . No special processing of the body anywhere along that path. The only special treatment prior to step 10 is about choosing a default constructor body if a constructor method isn't provided in the class declaration.

Fair point, things have changed. Still, here are some other bits of magic that make constructor(){} as ClassElement not just a method definition. Here's another:

  • 'constructor' is not enumerable but methods are.

Yes, but that's just preserving the 'constructor' property attribute conventions established by function definition. Nothing to do with the semantics of the actual constructor body or that a "class" object".

It is an alternative syntax for providing a the body of a function, but it has no unique semantics. That seems significant to me.

Not so much to me, and one could argue for static @@new{} sugar similarly.

I think that fact that these really are semantically equivalent:

function F(x) {this.foo=}; class F {constructor(x) {this.foo=x}};

is pretty important to the evolutionary nature ES6 classes.

The most important thing here (I agree with Andreas R.) is -- if possible -- avoiding uninitialized object observability.

I agree that uninitialized observability is a pain and has been a on-going source of reentrancy bugs in the the more complex built-in constructors. I want to explore whether making the constructor arguments available to @@create provides an alternative way to eliminate that issue.

Also, much of the initialization checking complexity in the legacy built-ins is about maintain backwards compatibility for edgy use cases (for example, installing a built-in constructor that has factory called behavior as an instance method on one of its instances and then invoking it as as a method). Jason's explicit split between "called as a function" and "called as a method" make it easier to specify that but I'm not sure the other forward facing complexities it introduces is worth the spec. simplification we get for a few legacy built-in constructors.

There is a lot of legacy compatibility vs. future functionality trade-offs that have been made in the current design. It's good to look at those trade-off and see if any of them need to be rebalanced. But we need to be careful about the scope of changes we are considering and whether in the end we actually get to a better place.

# Anne van Kesteren (7 years ago)

On Wed, Jun 18, 2014 at 9:01 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

Note that at this point we're already forcing spec authors to think about the exact @@create behavior to make things subclassable and that in particular, every single existing spec that has a constructor will need to be revisited and modified as needed.

Revisiting existing classes and making them suitable for subclassing seems like something that would be hard to avoid. Allen had to do it for all the built-in classes to make sure certain methods return this.constructor and such, I doubt we're going to avoid that.

# C. Scott Ananian (7 years ago)

On Wed, Jun 18, 2014 at 3:08 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

I think that fact that these really are semantically equivalent:

function F(x) {this.foo=x}; class F {constructor(x) {this.foo=x}};

is pretty important to the evolutionary nature ES6 classes.

That's the easy case. The class F version in Jason's proposal invokes a superclass constructor, but that does nothing so it's all the same.

The harder question is:

function F(x) { this.x = x; }
function G(y) { this.y = y; }
G.prototype = new F;

vs

function F(x) { this.x = x; }
class G extends F { constructor(y) { this.y = y; } }
# Brendan Eich (7 years ago)

Allen Wirfs-Brock wrote:

Not so much to me, and one could argue for static @@new{} sugar similarly.

I think that fact that these really are semantically equivalent:

function F(x) {this.foo=x}; class F {constructor(x) {this.foo=x}};

is pretty important to the evolutionary nature ES6 classes.

Jason covered the combinations: his proposal supports class subclassing function, etc. What concretely do you mean here, if not that? If you mean refactoring from one to the other, what observably differs?

Again it's important to separate independent parts of the counter-proposal (which even Jason is changing, e.g. no diff between C() and new C()). The high order bit remains the uninitialized observability of the draft-ES6 way.

# Boris Zbarsky (7 years ago)

On 6/18/14, 3:14 PM, Anne van Kesteren wrote:

Revisiting existing classes and making them suitable for subclassing seems like something that would be hard to avoid.

I think the difference for me is whether making a class subclassable before careful auditing means potentially introducing suboptimal behavior (that we presumably fix when either we do the audit or someone reports a bug) or whether it means potentially introducing a security bug.

In the former case, we can conceivably allow subclassing immediately (possibly only in nightly builds or whatnot) and then work on resolving the issues people find. In the latter case the auditing needs to be a lot more stringent before subclassing is allowed, and allowing subclassing without auditing is just a non-starter. I'm OK shipping somewhat buggy code for people to experiment with in nightly builds, but I'm not OK shipping security bugs.

# Allen Wirfs-Brock (7 years ago)

On Jun 18, 2014, at 1:43 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

Not so much to me, and one could argue for static @@new{} sugar similarly.

I think that fact that these really are semantically equivalent:

function F(x) {this.foo=x}; class F {constructor(x) {this.foo=x}};

is pretty important to the evolutionary nature ES6 classes.

Jason covered the combinations: his proposal supports class subclassing function, etc. What concretely do you mean here, if not that? If you mean refactoring from one to the other, what observably differs?

My understanding of the proposal was that:

class F {constructor(x) {this.foo=x}};

turns into the equivalent of:

function F(x) { /* ??? the initial message really isn't explicit about what does here */ }; F[Symbol.new] = {Symbol.new { //use an object literal to create a "method" kind of function var obj = super(); obj.foo = x }}[Symbol.new].toMethod(F);

which is quite different from you get for:

function F(x) {this.foo=x};

# Kevin Smith (7 years ago)

function F(x) { /* ??? the initial message really isn't explicit about what does here */ }; F[Symbol.new] = {Symbol.new { //use an object literal to create a "method" kind of function var obj = super(); obj.foo = x }}[Symbol.new].toMethod(F);

which is quite different from you get for:

function F(x) {this.foo=x};

Yes - the proposed semantics are quite different. The whole thrust of the proposal is to fuse initialization and allocation, which is completely at odds with how user-defined "classes" work in ES5.

I think Allen's suggestion of providing constructor arguments to @@create is promising. That would allow the implementation to allocate and initialize an object in one go, if desired. That seems to embody the advantages of fusing initialization and allocation, without the headache for the user.

# Kevin Smith (7 years ago)

I think Allen's suggestion of providing constructor arguments to @@create

is promising. That would allow the implementation to allocate and initialize an object in one go, if desired. That seems to embody the advantages of fusing initialization and allocation, without the headache for the user.

Never mind, that doesn't work. Sorry for the noise.

# Allen Wirfs-Brock (7 years ago)

For background, here is the original proposal presentation about using @@create to enable subclassing of built-ins. meetings:subclassing_builtins.pdf

In particular see material that starts at slide 19. In this proposal, the initialized/uninitialized state isn't really about ensuring that an object has be correctly initialized. It's more about distinguishing calls to the the constructor as a factory(possibly a name-spaced factory or a factory installed as a method of another object) from calls (or super calls) to the constructor to initialize an instance.

# Jason Orendorff (7 years ago)

On Wed, Jun 18, 2014 at 5:29 PM, Kevin Smith <zenparsing at gmail.com> wrote:

I think Allen's suggestion of providing constructor arguments to @@create is promising. That would allow the implementation to allocate and initialize an object in one go, if desired. That seems to embody the advantages of fusing initialization and allocation, without the headache for the user.

Never mind, that doesn't work. Sorry for the noise.

I'd like to understand. Why doesn't it work?

# Kevin Smith (7 years ago)

I'd like to understand. Why doesn't it work?

Allen wondered the same, so I'll just paste my reply to him:

So I was a little embarrassed about not thinking it through, and wanted to just void the comment.

But specifically, if the subclass wants to have a different set of constructor arguments than its base class, then presumably the subclass would have to override @@create with that signature.

class MyMap extends Map { constructor(a, b, c) { super(); /* other initialization */ } static [Symbol.create](a, b, c) { return super(); } }

Is that right?

That seems like too much of a burden for the user. And besides, it pollutes the nice separation of responsibilities that we currently have.

The current design does posit uninitialized objects, which implementers are obviously uncomfortable with. Could it be that we just haven't yet developed the implementation techniques for dealing with such uninitialized objects? Or is there a real problem with being able to observe them?

# Jason Orendorff (7 years ago)

On Wed, Jun 18, 2014 at 4:09 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Jason covered the combinations: his proposal supports class subclassing function, etc. What concretely do you mean here, if not that? If you mean refactoring from one to the other, what observably differs?

My understanding of the proposal was that:

class F {constructor(x) {this.foo=x}};

turns into the equivalent of:

function F(x) { /* ??? the initial message really isn't explicit about what does here */ }; F[Symbol.new] = {Symbol.new { //use an object literal to create a "method" kind of function var obj = super(); obj.foo = x }}[Symbol.new].toMethod(F);

which is quite different from you get for:

function F(x) {this.foo=x};

But again, what user-observable difference are you pointing to? new F(0) in either case creates a new object inheriting from F.prototype, sets that object's "foo" property to 0, and returns that object. Subclassing either one is just class G extends F {...}.

As a side note, just because you can write something in a complicated way doesn't necessarily mean it's complicated. I think having F() and new F() do the same thing is simpler for users than having them do different things. Having a single hook for object creation is simpler than having two (like C++) or three (like Ruby and Smalltalk).

function F(x) {this.foo=x} is short, not simple.

# Jason Orendorff (7 years ago)

On Wed, Jun 18, 2014 at 4:09 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

function F(x) { /* ??? the initial message really isn't explicit about what does here */ };

I think focusing on what was in the initial message vs. what was explained later (or remains open for discussion) does us all a disservice. No proposal springs fully formed from Zeus' head. It's a process.

# André Bargull (7 years ago)

The most important thing here (I agree with Andreas R.) is -- if possible -- avoiding uninitialized object observability.

I agree that uninitialized observability is a pain and has been a on-going source of reentrancy bugs in the the more complex built-in constructors. I want to explore whether making the constructor arguments available to @@create provides an alternative way to eliminate that issue.

That means depending on the class/built-in a subclass needs to override either the @@create method or the constructor function or both?

For example let's take the Map built-in. The @@create method will initialise [[MapData]] to an empty list early on, that way Map instances are never observed uninitialised. Processing the iterable argument to fill the Map may either occur in @@create or in the Map constructor function. Subclasses will need to consult the specification to find out which method needs to be overridden if the iterable argument needs to be preprocessed.

For other (more special) built-ins like String/Number/Function/..., @@create will need to perform the complete initialisation including processing the arguments. Otherwise uninitialised objects are still observable. As a result subclasses are required to override @@create and overriding the constructor is a no-op (w.r.t. arguments handling).

# Brendan Eich (7 years ago)

Kevin Smith wrote:

The current design does posit uninitialized objects, which implementers are obviously uncomfortable with. Could it be that we just haven't yet developed the implementation techniques for dealing with such uninitialized objects? Or is there a real problem with being able to observe them?

André replied well, but I just wanted to confirm: this is not some shallow whiny-implementor-knee-jerk-reaction problem. It's a real problem, for JS programmers as well as implementors.

# Mark S. Miller (7 years ago)

From a security perspective as well, I have always been uncomfortable with

observable uninitialized objects, though I have been too lazy to try demonstrating a concrete attack.

# Jason Orendorff (7 years ago)

On Thu, Jun 19, 2014 at 8:39 AM, André Bargull <andre.bargull at udo.edu> wrote:

That means depending on the class/built-in a subclass needs to override either the @@create method or the constructor function or both?

For example let's take the Map built-in. The @@create method will initialise [[MapData]] to an empty list early on, that way Map instances are never observed uninitialised. Processing the iterable argument to fill the Map may either occur in @@create or in the Map constructor function.

Right. It should happen in the constructor. Map[@@create] can ignore its arguments and return an empty Map. Subclasses will not need to override @@create.

Subclasses will need to consult the specification to find out which method needs to be overridden if the iterable argument needs to be preprocessed.

Or just try it and see what happens, which is what people seem to do in practice. For Map, everything would just work.

However, TC39 could not then evolve Map[@@create] to take arguments in a later edition. Existing subclasses would rely on Map[@@create] ignoring its arguments.

For other (more special) built-ins like String/Number/Function/..., @@create will need to perform the complete initialisation including processing the arguments. Otherwise uninitialised objects are still observable. As a result subclasses are required to override @@create and overriding the constructor is a no-op (w.r.t. arguments handling).

All correct. :-|

For a time yesterday, I hoped that @@create + constructor would solve everything. Classes that need to fully initialize would use @@create; all other code would use constructor; nobody would have to care which. But Kevin's right: subclasses would need to care, and it's a burden.