hegel icon indicating copy to clipboard operation
hegel copied to clipboard

Multiple inheritance is possible in plain JavaScript. Type support in Hegel?

Open trusktr opened this issue 5 years ago • 3 comments

Hello! At the moment, I am able to use Proxy to implement a function called multiple() (with its unit tests) that can be used like this:

import {multiple} from 'lowclass'

class Foo {
  foo() {console.log('foo')}
}

class Yeah {
  yeah() {return 'yeah'}
}

class Bar extends Yeah {
  bar() {console.log('bar' + super.yeah())}
}

class Baz extends multiple(Foo, Bar, Yeah) { // extra Yeah is ignored
  baz() {console.log('baz ' + super.yeah())}
}

const b =  new Baz

// it works!
b.foo() // logs "foo"
b.yeah() // returns "yeah"
b.bar() // logs "bar yeah"
b.baz() // logs "baz yeah"

Hegel Try

In plain JavaScript this works great and is much more convenient than class-factory mixins. I run into some troubles with the types in TypeScript, mostly because features like protected and private can not be used in mapped types.

Hegel doesn't support protected, so no issue there, but Hegel doesn't have the same types as TS, so not sure how to do this yet.

See CombinedClasses type of my multiple.ts.

Here's what that type looks like currently in Hegel Try. And the code:

type CombinedClasses<T> = T extends [] | [undefined]
	? typeof Object
	: T extends Constructor[]
	? MixedArray<T>
	: typeof Object

Is is possible to achieve it in Hegel?

I would love to be able to do this in Hegel!

trusktr avatar May 20 '20 20:05 trusktr

For now, answer is No. Because inheritance can be only with one class (because of prototype). But for types (interfaces) we already have multiple inheritance.
Thank you for the question ^_^

JSMonk avatar May 20 '20 20:05 JSMonk

If you're curious about multiple(), it basically creates a virtual prototype tree. The internal Proxy traps fork the property lookup onto each class's prototype chains, and apply the correct receiver arguments to the underlying Reflect calls.

The end result is simple for the end user: it allows them to combine any classes they want, thus allow code organization to not be restricted by stiff and difficult-to-modify class hierarchies.

A perfect use case example is something like mixing a utility class onto an existing class. For example, a project might contain this code

import {BaseClass} from './somewhere'

class MyClass extends BaseClass { /* ... */ }

and the user would like to make it also emit events, and there's already the Node.js builtin module that exports EventEmitter, so it can be re-used:

import {multiple} from 'lowclass'
import {EventEmitter} from 'events' // <-- Node.js builtin module, it has no idea about the existence of the multiple() function
import {BaseClass} from './somewhere'

class MyClass extends multiple(EventEmitter, BaseClass) {
  /* ... now the class can emit events too ... */
}

Class-factory mixins, on the other hand, have to be pre-designed as such. With multiple() we can mix any classes, without having to convert them to class-factory mixins.

trusktr avatar May 20 '20 20:05 trusktr

Man, I daydream about how awesome it would be to have this multiple() inheritance be strictly typed.

trusktr avatar Jun 06 '20 07:06 trusktr