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

Jordan Harband ljharb at gmail.com
Thu Jul 28 05:01:53 UTC 2016


Could you not make `multiple` be a function that returns a Proxy, rather
than achieving multiple inheritance by copying properties?

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

> 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
>>>>
>>>
>>>
>>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160727/8bd23f5c/attachment-0001.html>


More information about the es-discuss mailing list