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

Reply via email to