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