ES6 classes: deferring the creation step
Brendan Eich
brendan at mozilla.org
Sat Jun 28 08:49:27 PDT 2014
I like it! Cc'ing others who may have missed it. Boris is DOM guru you seek.
Does it address the bound function issue you cited in the previous
thread? It appears not to, but I might be missing something (jetlag).
/be
Claude Pache wrote:
> Please do the following substitutions in my message:
>
> * "created-but-initialised" → "created-but-not-initialised"
> * "an ordinary object" → "an object (i.e. a value of type Object) that is not a Non-Constructed Object" (2x)
>
> —Claude
>
> Le 27 juin 2014 à 15:56, Claude Pache<claude.pache at gmail.com> a écrit :
>
>> In the currently specced design of classes, the fact that the creation step and the initialisation step of built-in object may be separated by arbitrary user code is thought to be problematic.
>>
>> Jason proposed a @@new behaviour in replacement of @@create that would avoid the issue [1]. Here is a counter-proposal (or an improved proposal, ad libidum), which is not tightly coupled to @@new. In fact, it is designed to (well, TBH, it happens to) just work in absence of @@create or @@new hook, although it is possible to introduce them.
>>
>> The basic idea is the following: the creation process (the @@create step) is deferred as late as possible.
>> It will appear that, for built-in base classes like Array, creation occurs just before initialisation, making the created-but-initialised state of such objects unobservable, at least in absence of user-overridable @@create hook.
>>
>> Non-Constructed Objects
>> -----------------------
>>
>> Non-Constructed Objects are introduced in order to describe the state of not-yet-defined this-bindings.
>>
>> Non-Constructed Objects is probably not be the greatest approach to spec the thing, especially when considering the awful Step 3 of InitializeThisBindings() algorithm below;
>> it is just a convenient hack that allows me to expose the idea with minimal change from the current specification draft.
>>
>> A Non-Constructed Object is a special placeholder exotic object with the following internal slots:
>>
>> * [[Constructor]], which holds a reference to the constructor;
>> * [[NonConstructed]], set to `true`.
>>
>> Non-Constructed Objects may only appear as value of this-bindings inside functions
>> (or, using the spec language, as value of the `thisValue` component of a function environment record).
>> Any attempt to get an explicit reference to such an object in user code will throw an error.
>> The only way to pass (implicitely) a reference to a Non-Constructed Object between different function environment records,
>> is through calls to `super`.
>>
>> InitializeThisBindings(nonconstructedObj, obj) abstract operation
>> ----------------------------------------------------------------------
>>
>> This operation performs the actual initialisation of the this-bindings that were previously deferred:
>>
>> 1. Assert `nonconstructedThisObj` is a Non-Constructed Object.
>> 2. Assert `obj` is an ordinary object.
>> 3. Replace all references to `nonconstructedThisObj` with references to `obj`.
>> (In particular, this step will effectively initialise the this-binding of every function environment record that used to reference `nonconstructedObj`.)
>>
>> Additional runtime semantics of the `this` keyword
>> --------------------------------------------------
>>
>> Any attempt to get an explicit reference to the `thisValue` of a function environment record while it holds a Non-Constructed Object, shall throw an error.
>>
>>
>> new C(...args)
>> ----------------
>>
>> When a constructor `C` is called with arguments `args`, the following steps are taken.
>> In particular, the actual initialisation of the this-value is deferred.
>>
>> 1. Let `thisValue` be a new Non-Constructed Object, with its internal slot [[Constructor]] set to `C`.
>> 2. Let `R` be the result of `C`.[[Call]](`thisValue`, `args`).
>> 3. NOTE. The operation InitializeThisBinding() may have been called during the previous step, meaning that `thisValue` may now be a regular object.
>> 4. ReturnIfAbrupt(`R`).
>> 5. If Type(`R`) is Object, return `R`.
>> 6. If `thisValue` is a Non-Constructed Object, throw an error.
>> 7. Return `thisValue`.
>>
>> No more [[Construct]]
>> ----------------------
>>
>> [[Construct]] internal method is gone. Actually it is conflated with the [[Call]] internal method, modified as below.
>> The key fact is that [[Call]] is able to see if it should have a [[Construct]]-like behaviour,
>> by examining whether its `thisArgument` is a Non-Constructed Object.
>>
>>
>> F.[[Call]] (thisArgument, argumentsList) for user-defined functions
>> -----------------------------------------------------------------------------------
>>
>> User-defined functions has the currently specced [[Call]] behaviour [Section 9.2.2],
>> with the following additional step inserted somewhere near the beginning of the algorithm, e.g., after step 1:
>>
>> 1bis. If the `thisArgument` is a Non-Constructed Object,
>> a. If `F`’s [[NeedsSuper]] internal slot is set to false (IOW, if `F`’s code doesn’t contain `super`),
>> i. Let `proto` be the result of GetPrototypeFromConstructor(`thisArgument`.[[Constructor]], "%ObjectPrototype%").
>> ii. ReturnIfAbrupt(`proto`).
>> iii. Let `obj` be ObjectCreate(`proto`).
>> iv. Perform InitializeThisBindings(`thisArgument`, `obj`).
>> v. Assert: Now, `thisArgument` is an ordinary object.
>> b. Else, `thisArgument` is left untouched. // it is meant to be handled at the occasion of the enclosed `super` call.
>>
>>
>> F.[[Call]] (thisArgument, argumentsList) for bound functions
>> -------------------------------------------------------------
>>
>> In the algorithm sepcced in [Section 9.4.1.1], step 2 is replaced with:
>>
>> 2. If the `thisArgument` is a Non-Constructed Object, let `boundThis` be `thisArgument`. // this is the current [[Construct]] behaviour
>> 2bis. Else, let `boundThis` be the value of `F`’s [[BoundThis]] internal slot. // this is the current [[Call]] behaviour
>>
>>
>> F.[[Call]] (thisArgument, argumentsList) for the built-in Object constructor
>> -----------------------------------------------------------------------------
>>
>> There is no change: `Object(...)` acts as a factory rather than as a constructor, as currently specified, and the `thisArgument` is ignored.
>> In particular trying to subclass `Object` will lead to unexpected results. Note however that `new Object` does still work.
>>
>>
>> F.[[Call]] (thisArgument, argumentsList) for the built-in Array contsructor
>> ---------------------------------------------------------------------------
>>
>> The [[ArrayInitializationState]] internal slot is gone, and Step 4 of the algorithms in [Sections 22.1.1.*] is replaced with (where `O` is the this-value):
>>
>> 4. If `O` is a Non-Constructed Object,
>> a. Let `proto` be the result of GetPrototypeFromConstructor(`O`.[[Constructor]], "%ArrayPrototype%").
>> b. ReturnIfAbrupt(`proto`)
>> a. Let `array` be ArrayCreate(<<length>>, `proto`).
>> b. Perform InitializeThisBindings(`thisArgument`, `array`)
>> 5. Else, etc.
>>
>> The [[Call]] behaviour of other built-in constructors is left as an exercise to the reader.
>>
>> Comments
>> --------
>>
>> There is a nice side-effect of the proposal: The new internal check intended to discriminate between call-as-function and call-as-constructor is easier and more robust. In particular,
>>
>> * hacks such as [[ArrayInitializationState]] are no longer needed;
>> * bound functions are truly subclassable (see [2]).
>>
>> However, it remains very hard for user-defined functions to distinguish correctly between constructor/initialisation-calls and method/function-calls, or to write code that works well in both cases. (At least the situation is not worse than in ES5-.)
>>
>> Optional: the @@create hook
>> ----------------------------
>>
>> A @@create hook can easily be placed as follows:
>> In each [[Call]] internal methods defined above, a call to (`thisArgument`.[[Constructor]]).@@create
>> could replace the steps spanning from GetPrototypeFromConstructor(...) inclusive to InitializeThisBindings(...) exclusive.
>>
>> Whether such a hook is compatible with, e.g., DOM constructors, is left to the appreciation of the competent people. At worst, a built-in constructor could cheat by defining its own [[Call]] internal method that would refuse to run the @@create hook.
>>
>> Optional: the @@new hook
>> -------------------------
>>
>> Alternatively, the following hook may be installed:
>> At the beginning of each call to F.[[Call]], the following steps are taken:
>>
>> 1. if `thisArgument` is a Non-Constructed Object,
>> a. If `F` has an *own* property named @@new,
>> i. Let `R` be the result of `F`[@@new].[[Call]](`thisArgument`.[[Constructor]], `argumentsList`).
>> ii. ReturnIfAbrupt(`R`).
>> iii. If Type(`R`) is Object,
>> α. InitializeThisBindings(`thisArgument`, `R`).
>> iv. Return `R`.
>> b. etc.
>> 2. etc.
>>
>> Note that we don’t look for inherited @@new property, in order to preserve the initialise-at-latest-time behaviour.
>>
>> —Claude
>>
>> [Section 9.2.2]: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-ecmascript-function-objects-call-thisargument-argumentslist
>> [Section 9.4.1.1]: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-call
>> [Sections 22.1.1.*]: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-array-constructor
>> [1]: http://esdiscuss.org/topic/new
>> [2]: http://esdiscuss.org/topic/issue-when-subclassing-a-bound-function-used-as-constructor
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
More information about the es-discuss
mailing list