Jan 29 TC39 Meeting Notes

Herby Vojčík herby at mailbox.sk
Sat Feb 9 03:44:06 PST 2013



Rick Waldron wrote:
> # January 29 2013 Meeting Notes
>
> * @@create is a well known symbol that when used as a property of  a
> constructor provides the alloca1on method for that constructor.
> * New definition of the ordinary [[Construct]] :
>    1. Let creator be [[Get]] of this constructors @@create property
>    2.  If creator is not undefined, then
>    a. Let obj be the result of calling creator with this constructor as
> its this value.
>    3.  Else, fallback implementation
>    a. Let obj be a new ordinary object with its [[Prototype]]
> initialized from this constructor's “prototype” property.
>    4. Call this constructor with obj as the this value and the original
> argument list.
>    5.  Return obj.
>
> BE, EA, YK, WH, others: Get rid of the fallback implementation and just
> throw if there is no @@create. It's unnecessary complexity.
>
> * Most constructors just inherit Function.prototype[@@create]  which
> allocates a new ordinary object with its [[Prototype]]  initialized from
> this.prototype (the constructor’s “prototype”  property).
> * `new Foo() <=> Foo.call(Foo[@@create]())`
There are ...args, so this is in fact
   `new Foo(...args) <=> Foo.call(Foo[@@create](), ...args)`

I would argue this is wrong semantic, it should be
   `new Foo(...args) <=> Foo[@@create]().constructor(...args)`

At least for builtins are classes, this is attainable. For userland 
contructor functions, this would break, so they probably need the old 
semantics; but there is yet another possibility, I'll show it below[1].

> **Conclusion/Resolution**
> Consensus on @@create, allocation and initialization decoupling.

Let us assume we want to simulate classic class-based OOP semantics.
There are three roles here, they should be explicitly declared to the 
members of the show:
  * Class identity: this is one that
    * holds statics
    * is used in `new Foo`
    * is used in `extends Foo`
    This is currently played by the function Foo, with [[Construct]].
    Note: deliberately missing [[Call]]ability. It is not really needed.
      More[2] below.
  * Allocator of new instance
    Makes the new uninitalized instance, with necessary magic.
    This is now played by Foo[@@create].
  * Initializer of an instance: this is one that
    * initializes new instance of Foo in `new Foo`
    * initializes new instance of SubFoo
      in `super(...args)` in `new SubFoo`.
    (do someone really think these are separate? I think they are the same)
    This is schizophrenic now. Sometimes, this is played by function Foo,
      sometimes this is played by (potetially different or nonexisting)
      `Foo.prototype.constructor`.

Plus, there is fourth thing/role: knowledge own class. An instance wants 
to know its class (as it "Class identity" described above), to have 
access to its own statics (dynamically) and ability to use `new`.

This is much like smalltalk's `#class`.

It is now played by `constructor`, but very poorly, since it is often 
missing because of rewriting `.prototype`.

All of these four thing are needed; and third is schizophrenic and 
fourth is missing.

I think the object model regarding `new` etc. should be reformed a bit 
more (@@create was great first step).

By my suggestion of
   `new Foo(...args) <=> Foo[@@create]().constructor(...args)`
I tried to solve the schizophreny of the third one. If this could be 
attained, the third role could _always_ be played by 
`Foo.prototype.constructor`.

To solve the fourth, I would propose (at least a though experiment) in 
which it is an invariant that (new Foo).constructor === Foo`, that is, 
`constructor` always points to the creator class in case of "instances" 
created via `new`.

For `class`es (and builtins) this can be achieved easily: write-protect 
`Foo.prototype` (it is in case of `class` and I would presume it is for 
builtins, as well) and write-protect `Foo.prototype.constructor` as well.

For "free" userland constructor functions, this is more of a problem. 
They can have their .prototype rewritten, and / or .constructor with 
their .prototype. Now I propose the little breaking change (I would like 
to hear how much breaking it is, if you have the opinion, or even data): 
legacy constructor functions will have not only .prototype create, but 
also [@@behaviour] object behind the scenes where:
  * `Foo[@@behaviour].__proto__ === Foo.prototype` (whenever the 
Foo.prototype is changed, new [@@behaviour] should be recreated)
  * default Foo[@@create] would set __proto__ of new instance to 
[@@behaviour], not to .prototype.
  * [@@behaviour] will have write-protected .constructor pointing to `Foo`

An obvious optimization which saves a lot of unnecessary allocation is: 
only create [@@behaviour] lazily when first `new Foo` actually happens 
(majority of constructor function never really [[Construct]]).

(For classes and builtins, let's say [@@behaviuor] will always return 
.prototype)

What this means, (new Foo).constructor is always Foo. The question is: 
is this too breaking? Is there a code that changes .constructor 
legimately (to have, say, "species")?

The big plus would be, this would solve the inconsistencies and open 
questions. With this, it would be clear that:
  * Class identity is `Foo`
  * Allocator is `Foo[@@create]`
  * Initializer is either:
    * `Foo`, or
    * `Foo[@@behaviour].constructor`
    One can choose one and follow it consistently. But because of the 
way super is specced (super[[MethodName]]), the latter would be the choice.
  * Instance-class pointer is `constructor`.

[1] The above solves the problem that userland need legacy 
[[Construct]]. It does not, default [@@create] bases instances off 
[@@behaviour] which always have the right .constructor.

With these four roles on the table, one can envision that, later, one 
can split initialization from Class identity completely by adding 
writable [@@init] to [@@behaviour], initialized to `Foo` and stating that:
  * Initlalizer is `Foo[@@behaviour}[@@init]`.

[2] With the above [@@init] case, the [[Call]] is really not needed for 
`Foo` (unless you want to call it outside `new`, of course). Not in the 
case of using `new`/`super` consistently. The only need for it is (imo, 
maybe I overlook something) in the case of `SuperClass.apply(this, 
arguments)` legacy replacement of `super`. I do not think this is a 
problem - it can be replaced by `super[@@init](...arguments)`. Just 
showing the possibility - [[Construct]] bearing object does not need 
[[Call]] when it plays only Class identity role, it needs it only for 
Initializer role.

The rest would play nicely (specifically, the write-protected 
constructor, which will now play mainly the fourth role, to be allow for 
things like `new constructor(...args)` to create siblings).

To recap, I think the third and fourth (initializer and class-pointer) 
roles are insufficient as-is, and propose to think of two new changes:
  * `new Foo(...args) <=> Foo[@@create]().constructor(...args)`
  * adding [@@behaviour] with non-writable .constructor as the 
[[Prototype]] for new instances of userland contructor functions

The latter may be breaking, I'd like to ask how much, in your opinions.

> **Constructors need to be able to recognize initialized instances**

I would say no: the roles of Class identity and Initializer need to be 
recognized (they can be split by the way of [@@init] above, for example).

> [STH: conclusion/resolution: no new bit of state (IOW, I agree with WH)]

Thanks for not including the bit. I feel as wrong from the design point 
(not instance should know if it is initializing, but the process).

Herby


More information about the es-discuss mailing list