/** * outputs: * a { a: 3, b: 6, c: 12 } * b { a: 5, b: 10, c: 18 } * c { a: 8, b: 16, c: 27 } * { a: '25', b: '196', c: '900' } * c 900 */ // todo: use type system to statically verify the key exists in the object. type PointedObject<S extends string, T> = { [key:string]: T }; class Ctx<A> { constructor( public readonly key: string, protected sheet: PointedObject<typeof key,A> ) { if (! (key in sheet)) { throw new Error( `key ${key} does not exist in the properties object`); } const pds = Object.getOwnPropertyDescriptors(this); Object.defineProperties(this, { sheet: { ...pds.sheet, enumerable: false, writable: true } }); } get value() { return this.extract(); } get plain(): Record<string,A> { return { ...this.sheet }; } public map<B>(f: (a: A) => B): Ctx<B> { const key = this.key; const result : Partial<PointedObject<typeof key,B>> = {}; for (const [k,v] of Object.entries(this.sheet)) { Object.defineProperty(result, k, { enumerable: true, writable: true, value: f(v) }); } return new Ctx(this.key, result as PointedObject<typeof key,B>); } public extract(): A { return this.sheet[this.key]; } public duplicate(): Ctx<Ctx<A>> { const key = this.key; const sb: Partial<PointedObject<typeof key,Ctx<A>>> = {}; for (const [k,v] of Object.entries(this.sheet)) { Object.defineProperty(sb, k, { enumerable: true, value: new Ctx(k, this.sheet) }); } return new Ctx(this.key, sb as PointedObject<typeof key,Ctx<A>>); } public cothen<B>(fn: (c: Ctx<A>) => B): Ctx<B> { return this.duplicate().map(fn); } public apply<B>(fn: Ctx<(a: A) => B>): Ctx<B> { const key = this.key; const sb: Partial<PointedObject<typeof key,B>> = {}; for (const [k,v] of Object.entries(this.sheet)) { Object.defineProperty(sb, k, { enumerable: true, writable: true, value: fn.plain[k](v) }); } return new Ctx(this.key, sb as PointedObject<typeof key,B>); } } ///// // demonstration follows ///// // number context const ctx1 : Ctx<number> = new Ctx("a",{ a: 1, get b() { return this.b = this.a + this.a++; }, get c() { return this.c = this.a + this.b; } }); // applies an arbitrary transformation to each property. const ctx2 : Ctx<string> = ctx1 .cothen(({ key, plain, value }) => { console.log(key, plain); return value; }) .cothen(({ value }) => value * value) .cothen(({ value }) => value.toString()); console.log( ctx2.plain ); // safely change the key. const ctx3 : Ctx<string> = ctx2.duplicate().plain.c; console.log( ctx3.key, ctx3.value );