Last active
May 7, 2025 14:33
-
-
Save Fasteroid/2389c1cdbc43a4d046d3b3669dcf1be7 to your computer and use it in GitHub Desktop.
Multiple inheritance! The holy grail of programming!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| type Class = (abstract new (...args: any) => any) | |
| function pickFallback(sources: any[], prop: string | number | symbol) { | |
| for (let fallback of sources) { | |
| if( prop in fallback ){ | |
| return fallback; | |
| } | |
| } | |
| return undefined; | |
| } | |
| /** Creates a proxy handler which effectively simulates having multiple prototypes */ | |
| function FallbackProxyHandler<T extends object>(...sources: any[]): ProxyHandler<T> { | |
| return { | |
| get(target, prop, receiver) { | |
| if( Reflect.has(target, prop) ) return Reflect.get(target, prop, receiver); | |
| const fallback = pickFallback(sources, prop); | |
| if( fallback ) return fallback[prop]; | |
| return undefined; | |
| }, | |
| set(target, prop, value, receiver) { | |
| if( Reflect.has(target, prop) ) return Reflect.set(target, prop, value, receiver); | |
| const fallback = pickFallback(sources, prop); | |
| if( fallback ){ | |
| fallback[prop] = value; | |
| return true; | |
| } | |
| return Reflect.set(target, prop, value, receiver); | |
| }, | |
| has(target, prop) { | |
| // console.log( "HAS:", target, "has", prop, Reflect.has(target, prop) ) | |
| return Reflect.has(target, prop) || pickFallback(sources, prop); | |
| }, | |
| deleteProperty(target, prop) { | |
| throw new TypeError("Not applicable for multiple inheritance.") | |
| }, | |
| } | |
| } | |
| /** | |
| * Helper type to extract tuple of constructor parameters from an array of constructor types | |
| * @author Claude 3.7 Sonnet | |
| */ | |
| type ManyConstructorParameters<T extends Class[]> = { | |
| [K in keyof T]: ConstructorParameters<T[K]>; | |
| }; | |
| /** | |
| * Helper type to intersect the return values from a set of constructors | |
| * @author Fasteroid | |
| */ | |
| type CombinedConstructorResult<CLASSES extends Class[], RETURN extends unknown = unknown> = | |
| CLASSES['length'] extends 0 ? // are we done? | |
| RETURN // if so return | |
| : | |
| CLASSES extends [ | |
| abstract new (...args: any) => infer FIRST, | |
| ...infer REMAINING extends any[] | |
| ] | |
| ? | |
| CombinedConstructorResult<REMAINING, RETURN & FIRST> | |
| : | |
| RETURN | |
| ; | |
| /** Gets the full constructor signature for an amalgam class */ | |
| type MultipleInheritanceClass<BASES extends Class[]> = new (...args: ManyConstructorParameters<BASES>) => CombinedConstructorResult<BASES> | |
| /** | |
| * Creates a class with multiple base classes. | |
| * | |
| * Can be extended like any other normal class. | |
| * | |
| * Inherited properties are prioritized in order of the passed base classes to avoid the {@link https://en.wikipedia.org/wiki/Multiple_inheritance?useskin=vector#The_diamond_problem | diamond problem}. | |
| * @author Fasteroid, Claude 3.7 Sonnet | |
| */ | |
| export function amalgam<BASES extends Class[]>(...constructors: BASES): MultipleInheritanceClass<BASES> { | |
| return class { | |
| constructor(...args: ManyConstructorParameters<BASES>) { | |
| console.log(args, constructors) | |
| // Create instances of all provided constructors | |
| const instances = constructors.map((ctor, idx) => { | |
| // @ts-ignore | |
| return new ctor(...args[idx]); | |
| }); | |
| // Create a proxy that falls back through all instances in order | |
| const self = new Proxy({}, FallbackProxyHandler(...instances)); | |
| Object.defineProperties( self, Object.getOwnPropertyDescriptors( Object.getPrototypeOf(this) ) ); | |
| Object.setPrototypeOf(this, self); | |
| } | |
| } as MultipleInheritanceClass<BASES>; | |
| } | |
| class A { | |
| constructor(private input: string){} | |
| a() { console.log(this.input) }; | |
| } | |
| class B { | |
| b() { console.log("b") }; | |
| a(): number { return 1 } | |
| } | |
| class C { | |
| c() { console.log("c") }; | |
| } | |
| class X extends amalgam(A, B, C) { | |
| constructor(){ | |
| super(["a"],[],[]); | |
| this.a(); // console.log("a") | |
| } | |
| } | |
Author
Author
Author
If you need this to work with generics, you'll have to work around ts(2562)
Here's a "nice" way to do it:
const Amalgam =
( class extends amalgam(GenericThing) {} ) as
new <T>(...args: ManyConstructorParameters<[typeof GenericThing]>) => GenericThing<T>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I think I've peaked guys...