Skip to content

Instantly share code, notes, and snippets.

@sgoguen
Last active December 4, 2020 02:51
Show Gist options
  • Save sgoguen/dff10647f4115bb23960a956c19e0086 to your computer and use it in GitHub Desktop.
Save sgoguen/dff10647f4115bb23960a956c19e0086 to your computer and use it in GitHub Desktop.
Proof of concept for proxying lambdas
/*
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