Starting over again helps focus on core functionality. Dynamic polymorphism with external methods (no monkey-patching). Long ago I used "_extensions" in the current scope, but by using Symbols I can hide all the methods and not worry about variable name clashing.
let env: Env = {
a: new KopiNumber(5),
[KopiNumber.symbol]: KopiNumber.methods
};
async evaluate(env: Env): Promise<KopiValue> {
...
const method = env[leftValue.constructor.symbol)]
method.apply(undefined, [leftValue, rightValue])
}
This makes it easy to add or inject methods (limited type-classes) to any type. Sorry, no static typing in this version either. I'm focusing on semantics, but hopefully could be added.