The `super` keyword doesn't work as it should?

/#!/JoePea joe at trusktr.io
Thu Jul 28 03:19:29 UTC 2016


For reference, here's the implementation of `multiple()` where multiple
constructors calls are possible, using a `callSuperConstructor` helper:
https://gist.github.com/trusktr/05b9c763ac70d7086fe3a08c2c4fb4bf

*/#!/*JoePea

On Wed, Jul 27, 2016 at 8:16 PM, /#!/JoePea <joe at trusktr.io> wrote:

> For now, I've settled with writing classes like this:
>
> ```js
> const SomeClassMixin = base => {
>     class SomeClass extends base {
>         // ...
>     }
>
>     return SomeClass
> }
>
> const SomeClass = SomeClassMixin(class{})
> SomeClass.mixin = SomeClassMixin
>
> export {SomeClass as default}
> ```
>
> This makes it possible for an end user to import the class and extend from
> it like normal:
>
> ```js
> import SomeClass from './SomeClass'
>
> class OtherClass extends SomeClass {
>     // ...
> }
> ```
>
> But, it also allows and end user to use it as a mixin:
>
> ```js
> import SomeClass from './SomeClass'
> import AnotherClass from './AnotherClass'
>
> class OtherClass extends SomeClass.mixin(AnotherClass) {
>     // ...
> }
> ```
>
> This has two main downsides:
>
> - The class creator has to write the class as a class-factory mixin, then
> remember to export the mixin application on a plain `class {}`
> - It is not possible to pass specific arguments to the constructors of
> each class.
>
> One of my implementations of `multiple()` *does* allow one to pass
> arguments to each constructor. I'll re-visit that when Proxy is supported
> in Safari.
>
> I can't think of any way to make this work using a class helper library,
> because those need to create prototype chains, and thus `super` cannot be
> modified unless using `eval`, in which case original scope is lost.
>
> I literally think that a dynamic `super` and/or
> `Function.prototype.toMethod` would allow me to implement the ideal
> solution, which would have the following benefits:
>
> I am going to leave this alone for now, and just continue with the
> class-factory mixin approach, although that is not my ideal solution.
>
> Any ideas or suggestions?
>
> */#!/*JoePea
>
> On Wed, Jul 27, 2016 at 7:51 PM, /#!/JoePea <joe at trusktr.io> wrote:
>
>> > 1) There are indeed use cases for dynamically configuring the
>> HomeObject binding but they are quite specialized
>>
>> I suppose my case is quite specialized. I am wanting to duplicate the
>> prototype chains of ES6 classes, but I have no reliable (afaik) way to make
>> the `super` keyword work on copied methods. If I copy the method by
>> reference then the `super` keyword is based on the wrong `HomeObject`. If I
>> copy the method by getting its the source with `.toString()` followed by
>> using `eval` to define the new method on an object initialization, that
>> works and `super` will work, but this has problems if the original method
>> relied on variables in the outer scope where it was originally defined as
>> the copied method will not have access to that scope (f.e. code transpiled
>> by Babel may not be able to see the Babel helper functions).
>>
>> I am trying to implement a function `multiple()` so that I can do
>>
>> ```js
>> class SomeClass { ... }
>> class
>> ​OtherClass​
>>  { ... }
>> class Foo extends multiple(SomeClass, OtherClass) { ... }
>> ```
>>
>> and it's proving to be really difficult because I can't configure
>> `HomeObject`. The `multiple` function copies the prototype chains of each
>> class, then combines them together (when possible, otherwise an error is
>> thrown, depending on the native prototypes involved). As mentioned, this
>> somewhat works using `eval()` in the implementation, except that copied
>> methods don't work if they rely on variables of the outer scope where they
>> were originally defined. For example, suppose `SomeClass` in the
>> following example was imported from another file, and it depends on Babel
>> helpers defined in the original scope where it was imported from:
>>
>> ```js
>> import SomeClass from './somewhere/SomeClass'
>> class
>> ​OtherClass​
>>  { ... }
>> class Foo extends multiple(SomeClass, OtherClass) { ... }
>> ```
>>
>> The `multiple()` call will copy the methods of `SomeClass` onto a new
>> prototype chain (the methods need new `HomeObjects`), but the new methods
>> will fail to reference anything from the original scope where `SomeClass`
>> came from.
>>
>> I need to be able to modify `HomeObject`s without using an `eval()` hack
>> in order for my `multiple()` implementation to work 100% as intended.
>>
>> For now I may need to abandon my effort and use class-factory "mixins" as
>> in
>>
>> ```js
>> let SomeMixin = baseClass => class SomeMixin extends baseClass { ... }
>> ```
>>
>> , but the downside of this is that the classes are not usable as
>> standalone classes, so and end-user can't do
>>
>> ```js
>> class Foo extends SomeClass { ... }
>> ```
>>
>> We are required to do
>>
>> ```js
>> class Foo extends SomeClass(null) { ... }
>> ```
>>
>>  but due to the `null` the class we won't have `Object` methods. We could
>> do
>>
>> ```js
>> class Foo extends SomeClass(Object) { ... }
>> ```
>>
>> , but that is not also ideal as `Object` is not guaranteed to be the
>> native Object; `window.Object` could be overridden.
>>
>> I really want to define plain ES6 classes and use 3rd party plain ES6
>> classes (not class factories). It means that those classes are not limited
>> to being used as mixins.
>>
>> For reference, here's the implementation so far (with the ugly caveats of
>> cloning methods with `eval`):
>> https://gist.github.com/trusktr/8c515f7bd7436e09a4baa7a63cd7cc37
>>
>> I was planning to add caching so that combinations of classes could be
>> re-used (similar to what [mixwith.js](
>> https://github.com/justinfagnani/mixwith.js) does to cache mixin
>> applications).
>>
>> > We aren’t going to do anything until there is significant real world
>> feedback saying that it really is needed.
>>
>> Here's that feedback. :D
>>
>> TLDR, it seems that without flexibility in modifying `[[HomeObject]]` in
>> order to control what `super` references, I cannot implement an ideal
>> `multiple()`-inheritance helper that I would be able to comfortably suggest
>> for use in a production app. I would definitely not recommend the my
>> `eval`-based implementation as it will fail in ways that are very
>> undesirable and unexpected.
>>
>> With the limitation of a static `super`, I can't do something simple like
>> use `Object.assign` to simply copy some methods from one class' prototype
>> onto the class where I need them. That's normal pre-ES6 stuff that has
>> worked along with a dynamic `this` since forever, so the change in behavior
>> from `this` to `super` doesn't fit with the pre-ES6 language that we are
>> previously accustomed to and that we love (I do). ES6+ is
>> backwards-incompatible with some pre-ES6 paradigms.
>>
>> Pleeeease, let's add something like `toMethod`, or make `super` dynamic
>> (preferably both). I don't see any downside to having something like
>> `Function.prototype.toMethod`, as it is an opt-in type of feature, so it
>> won't bring with it performance loss like a dynamic `super` might (I still
>> haven't heard conclusive evidence regarding those performance losses).
>>
>> From what I can imagine, property lookup checks checks the current
>> object, then goes to the next prototype and does the same, until finally it
>> reaches a prototype where the property is found. Once that object (a
>> HomeObject) is found, we know what the `HomeObject` is. It seems very easy
>> to call a found method with that found `HomeObject`argument, and when the
>> method is called and if it uses `super`, then a similar search will happen
>> up the prototype chain until the matching super property is found. The
>> second prototype search (the one initiated due to the method using `super`)
>> is already a requirement, so, this means that the extra overhead for a
>> dynamic `super` stems from simply passing as argument the `HomeObject` of a
>> method once the method is found on a prototype chain. A static super means
>> that the `[[HomeObject]]` variable is simply defined forever instead of
>>  being marked in the property-lookup of a method, and that doesn't seem
>> like tons of extra overhead. Note that the property lookup is going to
>> happen anyways, so why not mark the `HomeObject` during the property lookup
>> algorithm?
>>
>> *Is that really enough overhead to merit a static `super`?* If someone
>> can explain it (or link to it), that would be greatly appreciated.
>>
>> I also think having dynamic `super` (or at least a way to configure it)
>> opens up language possibilities that can make interesting things happen
>> (for example, my `multiple()`-inheritance implementation would work
>> smoothly and instanceof checks would also work thanks to `@@hasInstance`).
>> The mere fact that a dynamic or configurable `[[HomeObject]]` would allow
>> for the creation of a tool like what I am trying to implement is good
>> enough reason to have the feature. Who knows what other awesome ideas
>> people will come up with.
>>
>> */#!/*JoePea
>>
>> On Sun, Jul 24, 2016 at 7:54 PM, Allen Wirfs-Brock <allen at wirfs-brock.com
>> > wrote:
>>
>>>
>>> On Jul 24, 2016, at 7:04 PM, /#!/JoePea <joe at trusktr.io> wrote:
>>>
>>> What if there was also something like `Function.prototype.bind` like
>>> `Function.prototype.with`, so `someFunc.with(homeObject)` returns a new
>>> function who's [[HomeObject]] is the specified `homeObject`. It would be
>>> possible to do `someFunc.with(...).bind(...)` to configure both the home
>>> object and `this`.
>>>
>>>
>>> This was  included in the ES6 drafst for quite awhile. Initially with
>>> the name defineMethod and latter as toMethod.  But it was eventually
>>> decided to remove it.  The pros and cons of such a function were
>>> extensively discussed with in TC39 over a several year period. For example,
>>>  see
>>> https://github.com/tc39/tc39-notes/blob/master/es6/2014-01/jan-28.md#more-on-tomethod
>>>
>>>
>>> The issues with defineMethod/toMethod are summarized in
>>> https://github.com/tc39/tc39-notes/blob/master/es6/2015-03/mixin-proposal.pdf
>>>  and https://github.com/allenwb/ESideas/blob/master/dcltomethod.md which
>>> is a proposal for an alternative way to address the problem. Notes from
>>> that discussion are at
>>> https://github.com/tc39/tc39-notes/blob/master/es6/2015-03/mar-25.md#6iv-a-declarative-alternative-to-tomethod-allen-wirfs-brock
>>>
>>>
>>> Where it has been left by TC30 is roughly:
>>> 1) There are indeed use cases for dynamically configuring the HomeObject
>>> binding but they are quite specialized.
>>> 2) All of the solution that have been proposed are confusing or error
>>> prone.
>>> 3) So far, the actual user demand for such a feature is small.
>>> 4) We aren’t going to do anything until there is significant real world
>>> feedback saying that it really is needed.
>>>
>>> Allen
>>>
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160727/f2a1865a/attachment-0001.html>


More information about the es-discuss mailing list