Transformer architecture implemented in TypeScript with WebGPU.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
nn/src/core.ts

236 lines
6.5 KiB

import { withLog } from './debug';
let index = 0;
export abstract class Term {
protected id = index++;
protected cache: Term | null = null;
public constructor(protected bindings: Record<string, Term>) { }
public refs(_name: string): boolean {
return false;
};
public bind(_name: string, _term: Term): Term {
return this;
}
public reduce(): Term {
return this;
}
public context(): string {
return !Object.keys(this.bindings).length ? `<${this.id}>` : `<${this.id}>[bound ${Object.keys(this.bindings).join(', ')}] `;
}
public stringify(context?: boolean): string {
return `${context ? this.context() : ''}${this.constructor.name}`;
}
public _(arg: RefTerm): Term {
return c(this, arg);
}
}
export class Undef extends Term {
constructor(
public from: string,
) { super({}); }
override reduce(): Term {
throw new Error(`${this.from} = undef`);
}
}
export class Native extends Term {
constructor(
public value: any,
) { super({}); }
public override stringify(): string {
return `\`${JSON.stringify(this.value)}\``;
}
}
export class Deferred extends Term {
constructor(
public func: (bindings: Record<string, Term>) => Term,
bindings: Record<string, Term> = {},
) { super(bindings); }
override refs(_name: string): boolean {
return true;
}
public override bind(name: string, term: Term): Term {
return this.bindings[name] || !this.refs(name)
? this
: new Deferred(this.func, { ...this.bindings, [name]: term });
}
public override reduce(): Term {
const term = this.cache
? withLog(
`Executing ${this.stringify()}`,
() => this.cache!,
r => `got ${r.stringify()} (cached)`,
)
: this.cache = withLog(
`Executing ${this.stringify()}`,
() => this.func({ ...globals, ...this.bindings }),
r => `got ${r.stringify()}`,
);
return Object.entries(this.bindings).reduce((t, b) => t.bind(...b), term).reduce();
}
public override stringify(context: boolean = true): string {
return `${context ? this.context() : ''}\`deferred\``;
}
}
export class Ref extends Term {
constructor(
public name: string,
bindings: Record<string, Term> = {},
) { super(bindings); }
override refs(name: string): boolean {
return this.name === name;
}
public override bind(name: string, term: Term): Term {
return this.bindings[name] || !this.refs(name)
? this
: new Ref(this.name, { [name]: term });
}
public override reduce(): Term {
if (this.cache) {
return withLog(
`Substituting ${this.stringify()}`,
() => this.cache!,
r => `with ${r.stringify()} (cached)`,
);
} else {
return this.cache = withLog(
`Substituting ${this.stringify()}`,
() => (this.bindings[this.name] ?? globals[this.name] ?? new Undef(this.name)).reduce(),
r => `with ${r.stringify()}`,
).reduce();
}
}
public override stringify(context = true): string {
return `${context ? this.context() : ''}${this.name}`;
}
}
export class Lambda extends Term {
constructor(
public param: string,
public body: Term,
bindings: Record<string, Term> = {},
) { super(bindings); }
override refs(name: string): boolean {
return this.param !== name && this.body.refs(name);
}
override bind(name: string, term: Term): Term {
return this.bindings[name] || !this.refs(name)
? this
: new Lambda(this.param, this.body.bind(name, term), { ...this.bindings, [name]: term });
}
public override stringify(context = true): string {
return this.body instanceof Lambda
? `${context ? this.context() : ''}\\ ${this.param} ${this.body.stringify(false).slice(2)}`
: `${context ? this.context() : ''}\\ ${this.param} . ${this.body.stringify(false)}`;
}
}
export class Call extends Term {
constructor(
public func: Term,
public arg: Term,
bindings: Record<string, Term> = {},
) { super(bindings); }
override refs(name: string): boolean {
return this.func.refs(name) || this.arg.refs(name);
}
override bind(name: string, term: Term): Term {
return this.bindings[name] || !this.refs(name)
? this
: new Call(this.func.bind(name, term), this.arg.bind(name, term), { ...this.bindings, [name]: term });
}
override reduce(): Term {
if (this.cache) {
return withLog(
`Reducing ${this.stringify()}`,
() => this.cache!,
r => `to ${r.stringify()} (cached)`,
);
} else {
return this.cache = withLog(
`Reducing ${this.stringify()}`,
() => {
const func = this.func.reduce();
if (func instanceof Lambda) {
return withLog(
`Applying ${func.stringify()} to ${this.arg.stringify()}`,
() => func.body.bind(func.param, this.arg),
r => `got ${r.stringify()}`,
).reduce();
}
return this;
},
r => `to ${r.stringify()}`,
);
}
}
public override stringify(context = true): string {
return `${context ? this.context() : ''}${this.func instanceof Lambda ? `(${this.func.stringify(false)})` : this.func.stringify(false)} ${this.arg instanceof Ref || this.arg instanceof Native ? this.arg.stringify(false) : `(${this.arg.stringify(false)})`}`;
}
}
const globals: Record<string, Term> = {};
type RefTerm = string | Term;
const termify = (t: RefTerm) => typeof t === 'string' ? r(t) : t;
export const _ = (v: any) => new Native(v);
export const r = (name: string): Ref => new Ref(name);
export const l = (param: string, body: RefTerm): Lambda => new Lambda(param, termify(body));
export const c = (func: RefTerm, arg: RefTerm, ...args: RefTerm[]): Call =>
args.reduce<Call>((f: Call, a: RefTerm) => new Call(f, termify(a)), new Call(termify(func), termify(arg)));
export const d = (func: (bindings: Record<string, Term>) => Term) => new Deferred(func);
export const $ = <T>(t: Term): T => {
const rt = t.reduce();
return rt instanceof Native ? rt.value : rt.stringify();
};
let cm: string = 'core';
export const m = (name: string) => cm = name;
export const g = (name: string, term: RefTerm) => {
name = `${cm}::${name}`;
if (globals[name]) {
throw new Error(`Duplicate global: "${name}"`);
}
globals[name] = termify(term);
return new Ref(name);
};
export const undef = g('undef', new Undef('undef'));
export const id = g('id', l('f', 'f'));
export const cnst = g('cnst', l('x', l('_', 'x')));
export const pipe = g('pipe', l('f', l('g', l('x', c('f', c('g', 'x'))))));