Last active
November 11, 2019 15:45
-
-
Save junlarsen/d02d3eed1cefd79916072a6522ef6ca9 to your computer and use it in GitHub Desktop.
Implementation of multi inheritance output for a transpiler in JS
This file contains 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
Save New Duplicate & Edit Just Text | |
// PoC for multiple class inheritance in JavaScript | |
// This is something your transpiler would emit | |
// | |
// If both super classes have a property then the baseclass | |
// must also have this property. Otherwise you will get ambiguous calls | |
// when calling BaseClass.BothParentsHaveThis() | |
interface Reference<T> { | |
thisRef: T | |
} | |
// Note: given the following scenario you must transpile to the super calls | |
// at compile time | |
// | |
// class A { | |
// method x() { print "Hi" } | |
// } | |
// | |
// class B extends A {} | |
// | |
// ; transpile this | |
// someB.x() | |
// | |
// Into this js code: | |
// | |
// someB.$parents.A.x() | |
interface Class<T> { | |
$parents: { | |
[_: string]: Class<T> | |
} | |
$instanceof(type: any): boolean | |
$constructor(ref: Reference<any>, ...args: any): void | |
} | |
// The following is the required boilerplate for a class | |
// You may omit the constructors if you want to | |
interface ReferenceA extends Reference<A> { } | |
class A implements Class<A> { | |
// List of superclasses | |
$parents = {} | |
// Need to do some compile time checks to translate alias imports | |
// otherwise stuff will probably break as type name is passed by | |
// string | |
$instanceof(type: any): boolean { | |
return type instanceof A | |
} | |
// Insert constructor body inside here, may add additional args with whatever | |
// the constructor will accept | |
$constructor(ref: ReferenceA /* , name: string */) { | |
} | |
// Should not be modifying the body of this constructor | |
// but you may add the additional args in here | |
constructor(/* name: string */) { | |
this.$constructor({ | |
thisRef: this | |
// Any additional args must be passed in here | |
} /* , name */) | |
} | |
// Declare a method for this class taking a single string argument | |
example(name: string) { | |
// Wrapping the function makes everything easier, just dump the | |
// transpiled code into the lambda | |
// First arg is the references, second is our user defined arg | |
const caller = (ref: ReferenceA, name: string) => { | |
console.log(`Class A said hello with ${name}`) | |
} | |
return caller({ | |
thisRef: this | |
}, name) | |
} | |
// Let's just declare a property on this class which we will mutate from class C | |
count = 0 | |
} | |
// Just another copy | |
interface ReferenceB extends Reference<B> { } | |
class B implements Class<B> { | |
$parents = {} | |
$instanceof(type: any): boolean { | |
return type instanceof B | |
} | |
$constructor(ref: ReferenceB) { | |
} | |
constructor() { | |
this.$constructor({ | |
thisRef: this | |
}) | |
} | |
// Also implement example in B to make C require it | |
example(name: string) { | |
const caller = (ref: ReferenceB, name: string) => { | |
console.log(`Class B said hello with ${name}`) | |
} | |
return caller({ | |
thisRef: this | |
}, name) | |
} | |
} | |
// A class which extends both of the above | |
interface ReferenceC extends Reference<C> { | |
refA: A | |
refB: B | |
} | |
// class C extends A, B | |
class C implements Class<C> { | |
$parents = { A: new A(), B: new B() } | |
$instanceof(type: any): boolean { | |
return type instanceof A | |
|| type instanceof B | |
|| type instanceof C | |
} | |
// Define a parameter age which is a number | |
$constructor(ref: ReferenceC, count: number) { | |
// Set super.A's count to this number | |
ref.refA.count = count | |
} | |
// Also define the argument here | |
constructor(count: number) { | |
this.$constructor({ | |
thisRef: this, | |
refA: this.$parents.A, | |
refB: this.$parents.B | |
}, count) | |
} | |
// Both superclasses have .example, we have to implement it here as well. | |
example(name: string) { | |
const caller = (ref: ReferenceC, name: string) => { | |
console.log(`Class C said hello with ${name}`) | |
// Let's also call both of the parents just for the sake of it | |
ref.refA.example(name) | |
ref.refB.example(name) | |
// Time to change A.count | |
ref.refA.count += 100 | |
} | |
return caller({ | |
thisRef: this, | |
refA: this.$parents.A, | |
refB: this.$parents.B | |
}, name) | |
} | |
} | |
// Create instance, which will set A.count to 100 | |
const example = new C(100) | |
// Print initial value ( 100 ) | |
console.log(example.$parents.A.count) | |
// Call the function which mutates it | |
example.example("supergrecko") | |
// Will now be 200 | |
console.log(example.$parents.A.count) | |
// Mutate it again | |
example.$parents.A.count = 300 | |
// And log it again, 300 | |
console.log(example.$parents.A.count) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment