Last active
December 4, 2020 02:51
-
-
Save sgoguen/dff10647f4115bb23960a956c19e0086 to your computer and use it in GitHub Desktop.
Proof of concept for proxying lambdas
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
/* | |
Typescript allows you to create types that transform existing types into new types. | |
Here's a TodoList class with a single readonly array property and two fluent methods. | |
*/ | |
class TodoList { | |
constructor(readonly items: ReadonlyArray<TodoItem> = []) { | |
} | |
addItem() { | |
return new TodoList([...this.items, new TodoItem()]); | |
} | |
f1(f: (x: number) => number) { | |
return this; | |
} | |
updateItem(pos: number, doChange: (item: TodoItem) => TodoItem) { | |
return new TodoList( | |
this.items.map((t, i) => (pos === i ? doChange(t) : t)) | |
); | |
} | |
} | |
const todo = new TodoList([]) | |
.addItem() | |
.updateItem(0, t => t.setDescription('Do this')); | |
// This special type, only extracts out all methods that return an instance of itself | |
type TodoListMethods = FluentMethods<TodoList>; | |
// This special type only extracts out the state | |
type TodoListState = ObjectState<TodoList>; | |
class TodoItem { | |
constructor( | |
public readonly description: string = "", | |
public readonly done: boolean = false | |
) { } | |
toggleComplete() { | |
return new TodoItem(this.description, !this.done); | |
} | |
setDescription(newDescription: string) { | |
return new TodoItem(newDescription, this.done); | |
} | |
} | |
type FluentMethodNames<T> = { | |
[K in keyof T]: T[K] extends ((...x: any) => T) ? K : never | |
}[keyof T]; | |
type FluentMethods<T> = { | |
[K in FluentMethodNames<T>]: T[K] extends ((...x: any) => T) ? T[K] : never | |
} | |
type BasicType = null | boolean | number | string | Date | BasicObject | ReadonlyArray<BasicType>; | |
interface BasicObject { | |
[key: string]: BasicType; | |
} | |
type P1 = PropertyNames<TodoList>; | |
type PropertyNames<T> = { | |
[K in keyof T]: T[K] extends Function ? never : K | |
}[keyof T] | |
type ObjectState<T> = Readonly<{ | |
[K in PropertyNames<T>]: T[K] extends Function ? never : T[K] | |
}> | |
type MessageMaker<T> = { | |
[K in FluentMethodNames<T>]: T[K] extends ((...x: infer P) => T) | |
? (...p: EncodeList<P>) => { type: K; payload: EncodeList<[...P]> } | |
: never | |
}; | |
type MessagePayload<T> = { | |
[K in keyof T]: T[K] extends ((...x: infer P) => T) | |
? { type: K; payload: P } | |
: never | |
}[keyof T]; | |
type EncodeOne<T> = | |
T extends ((item: infer P) => infer P) ? ((maker: MessageMaker<P>) => MessagePayload<P>) : T; | |
type EncodeList<T> = | |
T extends [infer Head] ? [EncodeOne<Head>] : | |
T extends [infer Head, ...infer Tail] ? [EncodeOne<Head>, ...EncodeList<Tail>] | |
: never; | |
function makeMessageMaker<T>(nested: number = 0): MessageMaker<T> { | |
const p = new Proxy( | |
{}, | |
{ | |
get: function (_target, property) { | |
return function () { | |
const allArguments = [...arguments].map(a => { | |
if (typeof a === 'function') { | |
return a(makeMessageMaker<any>(nested + 1)); | |
} | |
return a; | |
}) | |
const msg = { type: property, payload: allArguments }; | |
if (nested === 0) { | |
console.log(JSON.stringify(msg, null, 2)); | |
} | |
return msg; | |
}; | |
} | |
} | |
); | |
return p as MessageMaker<T>; | |
} | |
function test() { | |
const todoMessageMaker = makeMessageMaker<TodoList>(); | |
// todoMessageMaker.addItem(); | |
todoMessageMaker.updateItem(0, i => i.setDescription('New description 2')); | |
// console.log(m.setDescription(["New"])); | |
} | |
console.clear(); | |
// console.log('Hello') | |
test(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment