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
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss