T.J. Crowder (2017-01-06T10:31:00.000Z)
tj.crowder at farsightsoftware.com (2017-01-06T10:42:19.798Z)
On Thu, Jan 5, 2017 at 7:21 PM, James Treworgy <jamietre at gmail.com> wrote: > > Can you clarify what prevents it from being made to work? > > The fundamental feature difference (continuing to think about this!) is that > with ES5 constructors, I can create an instance of something from an abitrary > constructor provided to me, and inject properties that will be available to > it *at contruction time.* and On Fri, Jan 6, 2017 at 3:49 AM, Don Griffin <don at sencha.com> wrote: > ...the issue James mentioned with DI and I've hit with multiple-inheritance are > the restriction on ".call()" and ".apply()" being used on constructor functions. Thinking out loud: Both James' DI case and the multiple-inheritance case could be addressed with a Reflect utility function that allows you to provide a hook triggered when `this` is being allocated for the base constructor. This is basically `call` for constructors, but with a hook rather than a *thisArg*. Just to have a name/concept for it: `Reflect.new(targetConstructor, hook, ...args)`. For now let's assume `hook` is a simple function that receives the freshly-allocated `this` and can either augment it or return a replacement, but I'll circle back to that later. James' example: ```js let o = Reflect.new(Ctor, thisObj => { thisObj.logger = new Logger(); }); ``` Let's assume `Ctor` extends `Base`; it would work like this: 1. Call [EvaluateNew(`Ctor`, `args`, `hook`)][1], note the new third argument. EvaluateNew passes the hook to Construct, which passes it to [[Construct]]. 2. Eventually during that [[Construct]], `Ctor` calls `super`, which calls `Base`'s [[Construct]], also passing in the hook. (I don't know yet how [`super(...)`][4] has access to the hook; I guess we'd have to have it on the environment, like [[NewTarget]].) 3. We reach [Step 5][2] of `Base`'s [[Construct]] call. Since `Base`'s *kind* is "base", we perform OrdinaryCreateFromConstructor, but then pass the result through the hook. Since this particular hook doesn't return an object, *thisArgument* is set to the result from OrdinaryCreateFromConstructor as usual. 4. Construction completes as normal. So even the base constructor sees James' `logger` property on `this` by the time it has a `this`, because the hook gets a chance to augment it before the base constructor code runs. An MI example with `A`, `B`, and `C`, assuming they're all base constructors: ```js let o = Reflect.new(C, () => Reflect.new(B, () => new A() ) ); // (Presumably do some mixing in of prototype properties...) ``` Execution is like before, but in this case when we reach Step 5 of [[Construct]] for `B` and `C`, our hook returns an object, and so *thisArgument* is set to that object rather than the one from OrdinaryCreateFromConstructor. I said I'd circle back on hook being a simple function: The MI example above creates and throws away two objects it doesn't need to (the ones created for `B` and `C` but then replaced by the hook). If that's a concern, we can make hook an object with `allocate` and/or `postAllocate` properties: `allocate` would provide `this` (MI), `postAllocate` would just augment it (James' DI example). Or whatever. That's jumping forward to design; we're still at the concept stage and may well never reach design. If needed for things like `Error`, constructors could have a flag indicating that they cannot accept the hook (or at least, cannot accept the hook providing a different `this`), causing a throw at Step 5 of [[Construct]]. Conceptually simple. Not necessarily simple in terms of impacts on specification or implementations. In terms of the spec, we have at least: * Adding `Reflect.new` (or whatever it's called) * Modifying EvaluateNew ([here][1]) * Modifying Construct ([here][3]) * Modifying [[Construct]] ([here][2]) * Keeping track of the hook somewhere such that evaluating `super(...)` ([here][4]) can pass Construct the hook, possibly another slot on environment records * Possibly a flag slot or similar on functions like `Error`, if needed I'm not competent to speak to impacts on implementations. Which all sounds like a lot, but (modulo implementation complexity) I don't think it really is, and I think it's basically what we'd need to do to make `call`/`apply` work with constructors anyway (just passing around a hook function/object rather than a *thisArg*), since we wouldn't want `call`/`apply` to allow violating the "no `this` before `super(...)`" rule by setting the `this` binding early. -- T.J. [1]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-evaluatenew [2]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-ecmascript-function-objects-construct-argumentslist-newtarget [3]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-construct [4]: http://www.ecma-international.org/ecma-262/7.0/index.html#sec