This looks nice to me as well. But given the length I had trouble internalizing in what externally-visible ways it would change the status quo, especially with the optional @@create addition. The writeup as-is is very spec-language-focused. Would it be simple to summarize that for us?
There's also the question of what implementers think. To me it seemed a little more "weird" than @@create, such that the exact implementation details might not straightforwardly translate to implementations. But then again, implementers generally dislike @@create, so I don't think my intuition here is very good. -----Original Message----- From: es-discuss [mailto:[email protected]] On Behalf Of Claude Pache Sent: Friday, June 27, 2014 10:22 To: es-discuss Subject: Re: ES6 classes: deferring the creation step 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 <[email protected]> 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-fu > nction-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-constru > ctor > [1]: http://esdiscuss.org/topic/new > [2]: > http://esdiscuss.org/topic/issue-when-subclassing-a-bound-function-use > d-as-constructor _______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss _______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

