I wonder if we can do it.
I don't like class-factory mixins, because they require wrapping classes in functions. Therefore, one can not import a 3rd-party class and compose it.
I've implemented a multiple()
inheritance helper tool using Proxy
under the hood. It works like this:
import {multiple} from 'lowclass'
class One {one() {}}
class Two {two() {}}
class Three {three() {}}
class Three extends multiple(One, Two, Three) {
four() {
this.one()
this.two()
this.three()
}
}
That's a simple example, but you start to get the idea.
Implementation(s) are in lowclass/multiple.ts, and basic initial tests in lowclass/multiple.test.ts.
(Curious to know if you have any ideas on how to improve it!)
There are still a bunch of cases I need to add to the tests and account for in the implementation, but it works for simple use cases like the following:
Suppose we have the following code:
import {ExternalClass} from 'third-party-library'
class OurClass extends ExternalClass { ... }
Then, imagine that we want to add EventEmitter
functionality to OurClass
so it will emit events. We can do this simply:
import {ExternalClass} from 'third-party-library'
import {multiple} from 'lowclass'
import EventEmitter from 'events' // from Node.js
class OurClass extends multiple(ExternalClass, EventEmitter) {...}
const o = new OurClass
o.on('some-event', () => {...})
This is better than class-factory mixins for two main reasons (among others):
- all functionalities are written as plain
class
es. No wrapper-function boilerplate code. Simple. - import any 3rd-party
class
es and mix and match them. This is impossible with class-factory mixins.
TODOs:
- per-constructor args. This will give the ability to pass specific args to each constructor that a subclass extends from.
- statics: I haven't needed static inheritance yet so I haven't implemented that yet, but inevitably it'll be needed, and that's easy to implement.
- Use
Symbol.hasInstance
to makeinstanceof
work. - Diamond problem, or similar: give the ability to specify which super methods to call when there are name collisions in a hierarchy.
I am already familiar with proposal-mixins, but I don't like it because it's syntax for class-factory mixins. It means that we must explicitly choose between writing a class
or writing a mixin
. We get the same problem with 3rd-party code: we don't control the source, so we can't just convert a class
into a mixin
.
How would true multiple inheritance syntax look like?
Here's an idea, converting the same example from above:
import {ExternalClass} from 'third-party-library'
import EventEmitter from 'events' // from Node.js
// simple syntax
class OurClass extends ExternalClass, EventEmitter {...}
const o = new OurClass
o.on('some-event', () => {...})
I'd also need to iron out syntax for calling specific constructors with specific args and calling specific methods when there are name collisions.
Maybe this can eventually turn into a proposal, but I'd first like to iron out the behavior, and I'd also like to implement it in native code (fork Firefox, if I get that far).