new instantiation design alternatives

Allen Wirfs-Brock allen at wirfs-brock.com
Tue Sep 16 10:18:16 PDT 2014


On Sep 16, 2014, at 6:52 AM, Claude Pache wrote:

> 
> Le 15 sept. 2014 à 04:47, Domenic Denicola <domenic at domenicdenicola.com> a écrit :
> 
>> 
>> 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.
> 
> FWIW, it is quite near of what is done in PHP, which many web developers are familiar with:
> 
> * if you want to call the super (a.k.a. parent) constructor, you call it. If you don't call it, it won't be called. (It is the evident path to me, apparently it is not for some people used to other languages?);
> * you are not constrained to call the super constructor at the beginning, (or at the end, or whatever);
> * in any case, you get inheritance right. (It is a obligatory in PHP, and it should be the default in JS if you don't do something special.)
> 
> The main semantic difference is that the instance (`this`) is available in PHP before you call the super constructor.
> 
> —Claude
> 

Note that this also how the current plan of record for ES6 works and nobody has (at least recently) complained about it.  The motivating issue for the current discussion is that @@create can expose uninitialized instances.  

Even with the current ES6 design, we (almost) have a way around that.  There is no reason that a constructor has to used the `this` value produced via @@create.  For example, the Date constructor could simply ignore its initial `this` value and directly allocated a new Date instance.  For example:

Specification algoritm of a hypothetical Date constructor:
    1.  Let newDate be ObjectCreate(%DatePrototype%, ([[DateValue]]).
    2.  initialize newDate based upon the argument values
    3. Return newDate

or if self hosted in ES,
 class Date {
   constructor(...args) {
      //ignore this value passed in by new operator
      let newDate = privateAllocatedDateObject();
      //... initialize newDate using args
     return newDate
   }
 }

But there are still a couple problems with this approach in conjunction withf the current ES6 deisgn.
#1  The legacy behavior of Date is completely different when it is invoked as a function from what it is when it is invoked as a constructor.  So, the constructor algorithm has to be able to determine how it was invoked. For spec algorithms this is easy, we just write "if this function was invoked as a constructor, then ...".  But currently in ES6 self hosted code here is no way to make that determination.  Also, even in the current ES6 spec. code we have extra state and constructor logic to sort out situations where a constructor was "called as a function" but for the purpose of doing constructor initialization (eg, `super()` calls in a constructor) rather than for its actual "called as a function" behavior. 

#2 The above algorithm is hostile to subclassing Date.  The problem is that it sets the [[Prototype]] of the object it creates to Date.prototype but it really needs to be set to the subclasses prototype object. Currently ES6 has no way to communicate (even at the pseudo code) the actual invoked subclass constructor (or its prototype) to a superclass constructor that does internal allocation of this sort.  (In the current ES6 design, this is taken care of in @@create, but in this example we are avoiding @@create)

Claudes idea of extending [[Construct]] to include a "receiver" argument solves that problem.  It allows us to write the Data algorithm as:

    1.  If this function was invoked via [[Call]], then
         a.   do whatever
         b.   return something
    2.  Assert: this function was invoked via [[Construct]].
    3.  Let derivedConstructor be the receiver argument to this [[Construct]] call.
    4.  Let proto be Get(derivedConstructor,"prototype").
    5.  Let newDate be ObjectCreate(proto, ([[DateValue]]).
    6.  Initialize newDate based upon the argument values
    7.  Return newDate

For ES6 self hosted code, we hare proposing the `new^` that can be used to both discriminate  [[Call]] from [[Construct]] calls and to access the original constructor object.  So a ES6 self-hosted version of Date can be:

 class Date {
   constructor(...args) {
      if (new^) {
         //ignore this value passed in by new operator
         let newDate = privateAllocatedDateObject();
         Object.setPrototypeOf(newDate, new^.prototype);
         //... initialize newDate using args
         return newDate
      }
      /do whatever is required when Date is called as a function
      return something;
   }
 }

And a subclass constructor can be written as 

class SubDate extends Date {
   constructor(...args) {
        let newObj = new super(...args);
        // initialize any subclass specific instance state
        return newObj;
   }
}

Note that the explicit `new` is necessary because we need to invoke the super class constructor using [[Construct]].  The explicit `return` is necessary to make the subclass constructor return the object allocated and explicitly returned by the superclass constructor.  However, note that this subclass constructor, as written above, doesn't inherit the  any of the "called as a function" behavior.  If it wants that it would need to be written like:

class SubDate extends Date {
   constructor(...args) {
        if (new^) {
           let newObj = new super(...args);
           // initialize any subclass specific instance state
           return newObj;
        }             
        return super(...args);  //call superclass constructor "as a function"
        //perhaps do somethings before or after the above call.
   }
}

So, what the essentially of the new ES6 class allocation proposal (in all of its alternative forms) is to add `this^` and the receiver argument to [[Constructor]].  This is enough to solve the @@create exposing uninitialized instances issue. Whether we also get rid of @@create and whether we have a TDZ on `this` or allow assignment to `this` in constructors are non-essential secondary issues.  We can talk about them but we should let them become consensus breakers. 

Allen






More information about the es-discuss mailing list