move everything
This commit is contained in:
parent
3ff7e76694
commit
9050581a10
25 changed files with 37 additions and 42 deletions
635
src/eval/eval.ts
Normal file
635
src/eval/eval.ts
Normal file
|
|
@ -0,0 +1,635 @@
|
|||
// import { Double, eqType, fnType, getHumanReadableName, getSymbol, Int, isTypeVar, occurring, prettyT, recomputeTypeVarsWithInverse, substitute, symbolFunction, transitivelyGrow, trie, TYPE_VARS, UNBOUND_SYMBOLS } from "dope2";
|
||||
|
||||
// import type { ExprBlockState } from "./ExprBlock";
|
||||
// import type { InputValueType } from "./InputBlock";
|
||||
|
||||
// const IS_DEV = (import.meta.env.MODE === "development");
|
||||
// const VERBOSE = true && IS_DEV;
|
||||
|
||||
// export interface Environment {
|
||||
// names: any;
|
||||
// nextFreeTypeVar: number;
|
||||
// typeVars: Set<string>;
|
||||
// }
|
||||
|
||||
// interface Type {
|
||||
// symbol: string;
|
||||
// params: any[];
|
||||
// };
|
||||
|
||||
// type Unification = Map<string, any>;
|
||||
|
||||
// export interface DeepError {
|
||||
// kind: "error";
|
||||
// e: Error;
|
||||
// depth: number;
|
||||
// t: Type;
|
||||
// unification: Unification;
|
||||
// }
|
||||
|
||||
// // a dynamically typed value = tuple (instance, type)
|
||||
// export interface Dynamic {
|
||||
// kind: "value",
|
||||
// i: any;
|
||||
// t: Type;
|
||||
// unification: Unification;
|
||||
// };
|
||||
|
||||
// export interface Unknown {
|
||||
// kind: "unknown";
|
||||
// t: Type;
|
||||
// unification: Unification;
|
||||
// }
|
||||
|
||||
// // the value of every block is either known (Dynamic), an error, or unknown
|
||||
// export type ResolvedType = Dynamic | DeepError | Unknown;
|
||||
|
||||
// class NotFoundError extends Error {}
|
||||
|
||||
// export const evalExprBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => {
|
||||
// if (s.kind === "input") {
|
||||
// return evalInputBlock(s.text, s.value, env);
|
||||
// }
|
||||
// else if (s.kind === "call") {
|
||||
// return evalCallBlock(s.fn, s.input, env);
|
||||
// }
|
||||
// else if (s.kind === "let") {
|
||||
// return evalLetInBlock(s.value, s.name, s.inner, env);
|
||||
// }
|
||||
// else { // (s.kind === "lambda")
|
||||
// const [resolved, env2] = evalLambdaBlock(s.paramName, s.expr, env);
|
||||
// return [resolved, env2];
|
||||
// }
|
||||
// };
|
||||
|
||||
// export function evalInputBlock(text: string, value: InputValueType, env: Environment): [ResolvedType,Environment] {
|
||||
// if (value.kind === "literal") {
|
||||
// return parseLiteral(text, value.type, env);
|
||||
// }
|
||||
// else if (value.kind === "name") {
|
||||
// const found = trie.get(env.names)(text);
|
||||
// if (found) {
|
||||
// return [found, env]; // don't rewrite lambda parameters
|
||||
// }
|
||||
// }
|
||||
// const [t, env2] = makeTypeVar(env, 'err')
|
||||
// return [{
|
||||
// kind: "error",
|
||||
// t,
|
||||
// e: new NotFoundError(`'${text}' not found`),
|
||||
// depth: 0,
|
||||
// unification: new Map(),
|
||||
// }, env2];
|
||||
// }
|
||||
|
||||
// export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||
// let __debug = `
|
||||
// ========= evalCallBlock =========
|
||||
// env.typeVars : ${[...env.typeVars].map(getHumanReadableName)}
|
||||
// env.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])}
|
||||
// fn.kind : ${fn.kind}
|
||||
// input.kind : ${input.kind}`;
|
||||
// let [fnResolved, env2] = evalExprBlock(fn, env);
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// ==== evalCallBlock (fn) ====
|
||||
// fn.t : ${prettyT(fnResolved.t)}
|
||||
// env2.typeVars : ${[...env2.typeVars].map(getHumanReadableName)}
|
||||
// env2.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env2.nextFreeTypeVar])}`;
|
||||
// }
|
||||
// if (fnResolved.kind !== "unknown") {
|
||||
// let fnInverse;
|
||||
// [fnResolved, env2, fnInverse] = recomputeTypeVarsForEnv("<name unused>", fnResolved, env2);
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// --- recomp ---
|
||||
// fn.t : ${prettyT(fnResolved.t)}
|
||||
// fnInverse : ${[...fnInverse].map(([symbol,type])=>`${getHumanReadableName(symbol)} => ${getHumanReadableName(type)})`).join(',')}
|
||||
// env2.typeVars : ${[...env2.typeVars].map(getHumanReadableName)}
|
||||
// env2.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env2.nextFreeTypeVar])}`;
|
||||
// }
|
||||
// }
|
||||
// let [inputResolved, env3] = evalExprBlock(input, env2);
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// ==== evalCallBlock (input) ====
|
||||
// input.t : ${prettyT(inputResolved.t)}
|
||||
// env3.typeVars : ${[...env3.typeVars].map(getHumanReadableName)}
|
||||
// env3.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env3.nextFreeTypeVar])}`;
|
||||
// }
|
||||
// if (inputResolved.kind !== "unknown") {
|
||||
// let inputInverse;
|
||||
// [inputResolved, env3, inputInverse] = recomputeTypeVarsForEnv("<name unused>", inputResolved, env3);
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// --- recomp ---
|
||||
// input.t : ${prettyT(inputResolved.t)}
|
||||
// inputInverse : ${[...inputInverse].map(([symbol,type])=>`${getHumanReadableName(symbol)} => ${getHumanReadableName(type)})`).join(',')}
|
||||
// env3.typeVars : ${[...env3.typeVars].map(getHumanReadableName)}
|
||||
// env3.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env3.nextFreeTypeVar])}`;
|
||||
// }
|
||||
// }
|
||||
// const result = evalCallBlock2(fnResolved, inputResolved, env3);
|
||||
// if (VERBOSE) {
|
||||
// // @ts-ignore
|
||||
// result[0].__debug = __debug + (result[0].__debug || '');
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||
// if (getSymbol(fnResolved.t) !== symbolFunction) {
|
||||
// // not a function...
|
||||
// if (isTypeVar(fnResolved.t)) {
|
||||
// // ... but typeVars are OK (good guys!)
|
||||
// }
|
||||
// else {
|
||||
// // worst outcome
|
||||
// return makeError(env,
|
||||
// new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`),
|
||||
// mergeUnifications(fnResolved.unification, inputResolved.unification),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// // it's is a function, continue...
|
||||
// return evalCallBlock3(fnResolved, inputResolved, env);
|
||||
// };
|
||||
|
||||
// const highestTypeVar = type => {
|
||||
// let highest = -1;
|
||||
// for (const typeVar of occurring(type)) {
|
||||
// highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar));
|
||||
// }
|
||||
// return highest;
|
||||
// }
|
||||
// const highestTypeVar2 = typeVars => {
|
||||
// let highest = -1;
|
||||
// for (const typeVar of typeVars) {
|
||||
// highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar));
|
||||
// }
|
||||
// return highest;
|
||||
// }
|
||||
|
||||
|
||||
// const inverseUnification = (uni, inverse) => {
|
||||
// return new Map([...uni]
|
||||
// .filter(([symbol]) => !inverse.has(inverse.get(symbol)))
|
||||
// .map(([symbol, types]) => [inverse.get(symbol) || symbol, types])
|
||||
// );
|
||||
// }
|
||||
|
||||
// export function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, env: Environment): [ResolvedType,Environment,any] {
|
||||
// const [[newType], inverse] = recomputeTypeVarsWithInverse([resolved.t], findTempIdx(env));
|
||||
// const newResolved: ResolvedType = {
|
||||
// ...resolved,
|
||||
// t: newType,
|
||||
// unification: inverseUnification(resolved.unification, inverse),
|
||||
// };
|
||||
|
||||
// // hacky
|
||||
// const typeVars = env.typeVars.union(occurring(newType)) as Set<string>;
|
||||
// const newEnv: Environment = {
|
||||
// // names: trie.insert(env.names)(name)(newResolved),
|
||||
// names: env.names,
|
||||
// typeVars,
|
||||
// nextFreeTypeVar: highestTypeVar2(typeVars) + 1,
|
||||
// };
|
||||
|
||||
// return [newResolved, newEnv, inverse];
|
||||
// }
|
||||
|
||||
// function removeTypeVarsFromEnv(toRemove, env: Environment): Environment {
|
||||
// const newTypeVars = env.typeVars.difference(toRemove);
|
||||
// return {
|
||||
// names: env.names,
|
||||
// typeVars: newTypeVars,
|
||||
// nextFreeTypeVar: highestTypeVar2(newTypeVars) + 1,
|
||||
// }
|
||||
// }
|
||||
|
||||
// function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||
// let __debug = '';
|
||||
// try {
|
||||
// if (occurring(fnResolved.t).intersection(occurring(inputResolved.t)).size > 0) {
|
||||
// throw new Error(`Precondition failed: function (${prettyT(fnResolved.t)}) and its input (${prettyT(inputResolved.t)}) have overlapping typevars!`);
|
||||
// }
|
||||
|
||||
// const typeVarsOfFnAndInput = occurring(fnResolved.t).union(occurring(inputResolved.t));
|
||||
|
||||
// // turn input in to a function
|
||||
// const [abstractOutputType, env2] = makeTypeVar(env, "<out>");
|
||||
// const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType);
|
||||
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// ========= evalCallBlock3 =========
|
||||
// env.typeVars : ${[...env.typeVars].map(getHumanReadableName)}
|
||||
// nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])}
|
||||
// fnKind : ${fnResolved.kind}
|
||||
// inputKind : ${inputResolved.kind}
|
||||
// fnType : ${prettyT(fnResolved.t)}
|
||||
// inputType : ${prettyT(inputResolved.t)}
|
||||
// matchFnType : ${prettyT(matchFnType)}`;
|
||||
// }
|
||||
|
||||
// // unify both functions
|
||||
// const unification = /*transitivelyGrow*/(unifyLL(fnResolved.t, matchFnType));
|
||||
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// unification : ${prettyU(unification)}`;
|
||||
// }
|
||||
|
||||
// const unificationR = reduceUnification(unification);
|
||||
// const unifiedFnType = substitute(
|
||||
// // matchFnType,
|
||||
// fnResolved.t,
|
||||
// unificationR, []);
|
||||
|
||||
// const outType = unifiedFnType.params[1](unifiedFnType);
|
||||
|
||||
// const typeVarsOfOutType = occurring(outType);
|
||||
|
||||
// const removedTypeVars = typeVarsOfFnAndInput.difference(typeVarsOfOutType);
|
||||
|
||||
// // const newEnv = (outType === abstractOutputType) ? env2 : env;
|
||||
// const newEnv = removeTypeVarsFromEnv(removedTypeVars, env);
|
||||
|
||||
// // we don't want to 'bubble up' our outType substitution, because it's just a temporary variable
|
||||
// const unificationWithoutOutType = new Map([...unification].filter(([symbol]) => symbol !== abstractOutputType.symbol));
|
||||
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// unificationR : ${prettyRU(unificationR)}
|
||||
// unifiedFnType : ${prettyT(unifiedFnType)}
|
||||
// outType : ${prettyT(outType)}
|
||||
// removedTypeVars : ${[...removedTypeVars].map(getHumanReadableName)}
|
||||
// fn.unification : ${prettyU(fnResolved.unification)}
|
||||
// input.unification : ${prettyU(inputResolved.unification)}
|
||||
// unificationWithoutOutType: ${prettyU(unificationWithoutOutType)}`;
|
||||
// }
|
||||
|
||||
// const grandUnification = transitivelyGrow([fnResolved.unification, inputResolved.unification]
|
||||
// .reduce(mergeUnifications, unificationWithoutOutType));
|
||||
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// grandUnification : ${prettyU(grandUnification)}
|
||||
// newEnv.typeVars : ${[...newEnv.typeVars].map(getHumanReadableName)}
|
||||
// newEnv.newFreeTypeVar : ${getHumanReadableName(UNBOUND_SYMBOLS[newEnv.nextFreeTypeVar])}`;
|
||||
// }
|
||||
|
||||
|
||||
// if (inputResolved.kind === "error") {
|
||||
// // throw inputResolved.e;
|
||||
// return [{
|
||||
// kind: "error",
|
||||
// e: inputResolved.e, // bubble up the error
|
||||
// depth: 0,
|
||||
// t: outType,
|
||||
// unification: grandUnification,
|
||||
// // @ts-ignore
|
||||
// __debug,
|
||||
// }, newEnv];
|
||||
// }
|
||||
// if (fnResolved.kind === "error") {
|
||||
// // throw fnResolved.e;
|
||||
// // also bubble up
|
||||
// return [{
|
||||
// kind: "error",
|
||||
// e: fnResolved.e,
|
||||
// depth: fnResolved.depth+1,
|
||||
// t: outType,
|
||||
// unification: grandUnification,
|
||||
// // @ts-ignore
|
||||
// __debug,
|
||||
// }, newEnv];
|
||||
// }
|
||||
// // if the above statement did not throw => types are compatible...
|
||||
// if (inputResolved.kind === "value" && fnResolved.kind === "value") {
|
||||
// const outValue = fnResolved.i(inputResolved.i);
|
||||
// // console.log('outValue:', outValue);
|
||||
// return [{
|
||||
// kind: "value",
|
||||
// i: outValue,
|
||||
// t: outType,
|
||||
// unification: grandUnification,
|
||||
// // @ts-ignore
|
||||
// __debug,
|
||||
// }, newEnv];
|
||||
// }
|
||||
// else {
|
||||
// // we don't know the value, but we do know the type:
|
||||
// return [{
|
||||
// kind: "unknown",
|
||||
// t: outType,
|
||||
// unification: grandUnification,
|
||||
// // @ts-ignore
|
||||
// __debug,
|
||||
// }, newEnv];
|
||||
// }
|
||||
// }
|
||||
// catch (e) {
|
||||
// // if ((e instanceof UnifyError)) {
|
||||
// if (VERBOSE) {
|
||||
// __debug += '\n\nUnifyError! ' + (e as Error).message;
|
||||
// }
|
||||
// const err = makeError(env, e as Error);
|
||||
// // @ts-ignore
|
||||
// err[0].__debug = __debug;
|
||||
// return err;
|
||||
// // }
|
||||
// throw e;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||
// const [valueResolved] = evalExprBlock(value, env);
|
||||
// const innerEnv = makeInnerEnv(env, name, valueResolved);
|
||||
// return evalExprBlock(inner, innerEnv);
|
||||
// }
|
||||
|
||||
// const prettyRU = (rUni: Map<string, Type>) => {
|
||||
// return '{'+[...rUni].map(([symbol,type]) => `${getHumanReadableName(symbol)} => ${prettyT(type)}`).join(', ')+'}';
|
||||
// }
|
||||
|
||||
// function fixPoint(varName: string, expr: ExprBlockState, env: Environment) {
|
||||
// let [varType, innerEnv] = makeTypeVar(env, varName);
|
||||
// let round = 0;
|
||||
// let __debug = '';
|
||||
// while (true) {
|
||||
// const [innerResolved] = evalExprBlock(expr, innerEnv);
|
||||
// const newInnerT = substitute(innerResolved.t, reduceUnification(innerResolved.unification), []);
|
||||
|
||||
// round++;
|
||||
// if (round === 10) {
|
||||
// throw new Error("too many rounds!");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment,Environment] {
|
||||
// let [paramType, innerEnv] = makeTypeVar(env, paramName);
|
||||
// let round = 0;
|
||||
// let __debug = '';
|
||||
// while (true) {
|
||||
// // fixpoint computation...
|
||||
// const [exprResolved] = evalExprBlock(expr, innerEnv);
|
||||
// const lambdaT = fnType(_ => paramType)(_ => exprResolved.t);
|
||||
// // This is the only place in the code where we actually do something with the 'substitutions'. We compute the type of our lambda function:
|
||||
// const reduced = reduceUnification(exprResolved.unification);
|
||||
// const lambdaTSubstituted = substitute(
|
||||
// lambdaT,
|
||||
// reduced,
|
||||
// []); // <- not important
|
||||
|
||||
// let lambdaResolved: ResolvedType;
|
||||
// if (exprResolved.kind === "error") {
|
||||
// lambdaResolved = {
|
||||
// kind: "error",
|
||||
// t: lambdaTSubstituted,
|
||||
// depth: 0,
|
||||
// e: exprResolved.e,
|
||||
// unification: exprResolved.unification,
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// lambdaResolved = {
|
||||
// kind: "value",
|
||||
// t: lambdaTSubstituted,
|
||||
// i: (x: any) => {
|
||||
// const innerEnv = makeInnerEnv(env, paramName, {
|
||||
// kind: "value",
|
||||
// i: x,
|
||||
// t: lambdaTSubstituted,
|
||||
// unification: new Map(),
|
||||
// });
|
||||
// const [result] = evalExprBlock(expr, innerEnv);
|
||||
// if (result.kind === "value") {
|
||||
// return result.i;
|
||||
// }
|
||||
// },
|
||||
// unification: exprResolved.unification,
|
||||
// }
|
||||
// }
|
||||
|
||||
// // const [betterResolved, newInnerEnv] = recomputeTypeVarsForEnv("<name unused>", lambdaResolved, env);
|
||||
// const [betterResolved, newInnerEnv] = [lambdaResolved, env];
|
||||
|
||||
// const inferredParamType = betterResolved.t.params[0](betterResolved.t);
|
||||
|
||||
// if (VERBOSE) {
|
||||
// __debug += `
|
||||
// ====== evalLambdaBlock (round ${round}) ======
|
||||
// env.typeVars : ${[...env.typeVars].map(getHumanReadableName)}
|
||||
// nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])}
|
||||
// paramName : ${paramName}
|
||||
// paramType : ${prettyT(paramType)}
|
||||
// innerEnv.typevars : ${[...innerEnv.typeVars].map(getHumanReadableName)}
|
||||
// paramType : ${prettyT(paramType)}
|
||||
// exprType : ${prettyT(exprResolved.t)}
|
||||
// exprUnification : ${prettyU(exprResolved.unification)}
|
||||
// exprUnificationR : ${prettyRU(reduced)}
|
||||
// lambdaType : ${prettyT(lambdaT)}
|
||||
// lambdaTypeSubsituted: ${prettyT(lambdaTSubstituted)}
|
||||
// betterResolvedT : ${prettyT(betterResolved.t)}
|
||||
// inferredParamType : ${prettyT(inferredParamType)}`;
|
||||
// }
|
||||
|
||||
// if (eqType(paramType)(inferredParamType)) {
|
||||
// // reached fixpoint
|
||||
// // @ts-ignore
|
||||
// lambdaResolved.__debug = __debug;
|
||||
// return [lambdaResolved, env, innerEnv];
|
||||
// }
|
||||
// else {
|
||||
// paramType = inferredParamType;
|
||||
// innerEnv = {
|
||||
// ...newInnerEnv,
|
||||
// names: trie.insert(newInnerEnv.names)(paramName)({
|
||||
// kind: "unknown",
|
||||
// t: inferredParamType,
|
||||
// unification: new Map(), // <-- ??
|
||||
// }),
|
||||
// };
|
||||
// round++;
|
||||
// if (round === 10) {
|
||||
// throw new Error("too many rounds!");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] {
|
||||
// // dirty
|
||||
// if (type === "Int") {
|
||||
// return parseAsInt(text, env);
|
||||
// }
|
||||
// if (type === "Double") {
|
||||
// return parseAsDouble(text, env);
|
||||
// }
|
||||
// return makeError(env, new Error("Failed to parse"));
|
||||
// }
|
||||
|
||||
// function parseAsDouble(text: string, env: Environment): [ResolvedType,Environment] {
|
||||
// if (text !== '') {
|
||||
// const num = Number(text);
|
||||
// if (!Number.isNaN(num)) {
|
||||
// return [{
|
||||
// kind: "value",
|
||||
// i: num,
|
||||
// t: Double,
|
||||
// unification: new Map(),
|
||||
// }, env];
|
||||
// }
|
||||
// }
|
||||
// return makeError(env, new Error("Failed to parse as Double"));
|
||||
// }
|
||||
// function parseAsInt(text: string, env: Environment): [ResolvedType,Environment] {
|
||||
// if (text !== '') {
|
||||
// try {
|
||||
// return [{
|
||||
// kind: "value",
|
||||
// i: BigInt(text),
|
||||
// t: Int,
|
||||
// unification: new Map(),
|
||||
// }, env]; // may throw
|
||||
// }
|
||||
// catch {}
|
||||
// }
|
||||
// return makeError(env, new Error("Failed to parse as Int"));
|
||||
// }
|
||||
|
||||
// const literalParsers = [parseAsDouble, parseAsInt];
|
||||
|
||||
// export function attemptParseLiteral(text: string, env: Environment): Dynamic[] {
|
||||
// return literalParsers.map(parseFn => parseFn(text, env))
|
||||
// .map(([resolved]) => resolved)
|
||||
// .filter((resolved) => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
|
||||
// }
|
||||
|
||||
// export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) {
|
||||
// const bias = outPriority(resolved);
|
||||
|
||||
// if (resolved.kind === "value") {
|
||||
// return bias;
|
||||
// }
|
||||
// else if (resolved.kind === "unknown") {
|
||||
// return bias;
|
||||
// }
|
||||
// if (resolved.e instanceof UnifyError) {
|
||||
// return -1 + bias; // parameter doesn't match
|
||||
// }
|
||||
// else {
|
||||
// return -2 + bias; // even worse: fn is not a function!
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function makeInnerEnv(env: Environment, name: string, value: ResolvedType): Environment {
|
||||
// if (name !== "") {
|
||||
// return {
|
||||
// names: trie.insert(env.names)(name)(value),
|
||||
// nextFreeTypeVar: env.nextFreeTypeVar,
|
||||
// typeVars: env.typeVars,
|
||||
// }
|
||||
// }
|
||||
// return env;
|
||||
// }
|
||||
|
||||
// function findTempIdx(env: Environment) {
|
||||
// let idx = 16;
|
||||
// while (env.typeVars.has(UNBOUND_SYMBOLS[idx])) {
|
||||
// idx++;
|
||||
// }
|
||||
// return idx;
|
||||
// }
|
||||
|
||||
// export function makeTypeVar(env: Environment, name: string): [Type, Environment] {
|
||||
// let idx = 0;
|
||||
// while (env.typeVars.has(UNBOUND_SYMBOLS[idx])) {
|
||||
// idx++;
|
||||
// }
|
||||
// const typeVar = TYPE_VARS[idx];
|
||||
// const unknown: Unknown = {
|
||||
// kind: "unknown",
|
||||
// t: typeVar,
|
||||
// unification: new Map(),
|
||||
// };
|
||||
// return [ typeVar, {
|
||||
// names: trie.insert(env.names)(name)(unknown),
|
||||
// nextFreeTypeVar: idx + 1,
|
||||
// typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]),
|
||||
// }];
|
||||
// }
|
||||
|
||||
// function makeError(env: Environment, e: Error, unification: Unification=new Map()): [DeepError, Environment] {
|
||||
// let idx = 0;
|
||||
// while (env.typeVars.has(UNBOUND_SYMBOLS[idx])) {
|
||||
// idx++;
|
||||
// }
|
||||
// const typeVar = TYPE_VARS[idx];
|
||||
// const deepError: DeepError = {
|
||||
// kind: "error",
|
||||
// t: typeVar,
|
||||
// unification: new Map(),
|
||||
// e,
|
||||
// depth: 0,
|
||||
// };
|
||||
// return [deepError, {
|
||||
// names: env.names,
|
||||
// nextFreeTypeVar: idx + 1,
|
||||
// typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]),
|
||||
// }];
|
||||
// }
|
||||
|
||||
// export function removeFocus(state: ExprBlockState): ExprBlockState {
|
||||
// if (state.kind === "input") {
|
||||
// return { ...state, focus: false };
|
||||
// }
|
||||
// else if (state.kind === "call") {
|
||||
// return {
|
||||
// ...state,
|
||||
// fn: removeFocus(state.fn),
|
||||
// input: removeFocus(state.input),
|
||||
// };
|
||||
// }
|
||||
// else if (state.kind === "lambda") {
|
||||
// return {
|
||||
// ...state,
|
||||
// focus: false,
|
||||
// expr: removeFocus(state.expr),
|
||||
// };
|
||||
// }
|
||||
// else { // state.kind === "let"
|
||||
// return {
|
||||
// ...state,
|
||||
// focus: false,
|
||||
// value: removeFocus(state.value),
|
||||
// inner: removeFocus(state.inner),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function addFocusRightMost(state: ExprBlockState) : ExprBlockState {
|
||||
// if (state.kind === "input") {
|
||||
// return { ...state, focus: true };
|
||||
// }
|
||||
// else if (state.kind === "call") {
|
||||
// return {
|
||||
// ... state,
|
||||
// input: addFocusRightMost(state.input),
|
||||
// };
|
||||
// }
|
||||
// else if (state.kind === "lambda") {
|
||||
// return {
|
||||
// ...state,
|
||||
// expr: addFocusRightMost(state.expr),
|
||||
// };
|
||||
// }
|
||||
// else { // state.kind === "let"
|
||||
// return {
|
||||
// ...state,
|
||||
// inner: addFocusRightMost(state.inner),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
220
src/eval/infer_type.ts
Normal file
220
src/eval/infer_type.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import { Double, eqType, fnType, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, prettySS, recomputeTypeVars, substitute, trie, TYPE_VARS, UNBOUND_SYMBOLS, unify } from "dope2";
|
||||
|
||||
import type { CallBlockState } from "../component/expr/CallBlock";
|
||||
import type { ExprBlockState } from "../component/expr/ExprBlock";
|
||||
import type { InputBlockState } from "../component/expr/InputBlock";
|
||||
import type { LambdaBlockState } from "../component/expr/LambdaBlock";
|
||||
import type { LetInBlockState } from "../component/expr/LetInBlock";
|
||||
|
||||
export interface Environment {
|
||||
names: any;
|
||||
typevars: Set<string>;
|
||||
}
|
||||
|
||||
export interface Type {
|
||||
symbol: string;
|
||||
params: ((t: Type) => Type)[];
|
||||
};
|
||||
|
||||
export type Substitutions = Map<string, Type>;
|
||||
|
||||
export interface TypeInfo {
|
||||
type: Type;
|
||||
subs: Substitutions;
|
||||
newEnv: Environment;
|
||||
err?: IncompatibleTypesError;
|
||||
}
|
||||
|
||||
export interface TypeInfoLambda extends TypeInfo {
|
||||
paramType: Type;
|
||||
innerEnv: Environment;
|
||||
}
|
||||
export interface TypeInfoCall extends TypeInfo {
|
||||
inputEnv: Environment;
|
||||
}
|
||||
|
||||
export function inferType(s: ExprBlockState, env: Environment): TypeInfo {
|
||||
if (s.kind === "input") {
|
||||
return inferTypeInput(s, env);
|
||||
}
|
||||
else if (s.kind === "call") {
|
||||
return inferTypeCall(s, env);
|
||||
}
|
||||
else if (s.kind === "let") {
|
||||
return inferTypeLet(s, env);
|
||||
}
|
||||
else { // (s.kind === "lambda")
|
||||
return inferTypeLambda(s, env);
|
||||
}
|
||||
}
|
||||
|
||||
export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfo {
|
||||
if (s.value.kind === "literal") {
|
||||
const type = {
|
||||
Int: Int,
|
||||
Double: Double,
|
||||
}[s.value.type] as Type;
|
||||
return {
|
||||
type,
|
||||
subs: new Map(),
|
||||
newEnv: env,
|
||||
}
|
||||
}
|
||||
else if (s.value.kind === "name") {
|
||||
const found = trie.get(env.names)(s.text);
|
||||
if (found) {
|
||||
const [type, newEnv] = (found.kind === "unknown")
|
||||
? [found.t, env]
|
||||
: rewriteType(found.t, env);
|
||||
return {
|
||||
type,
|
||||
subs: new Map(),
|
||||
newEnv,
|
||||
};
|
||||
}
|
||||
}
|
||||
// kind === "text", or name not found
|
||||
const [type, newEnv] = typeUnknown(env);
|
||||
return {
|
||||
type,
|
||||
subs: new Map(),
|
||||
newEnv,
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
export function inferTypeCall(s: CallBlockState, env: Environment): TypeInfoCall {
|
||||
const fnTypeInfo = inferType(s.fn, env);
|
||||
const inputEnv = fnTypeInfo.newEnv;
|
||||
const inputTypeInfo = inferType(s.input, inputEnv);
|
||||
const [returnType, envWithReturn] = typeUnknown(inputTypeInfo.newEnv);
|
||||
const fakeFnType = fnType(_ => inputTypeInfo.type)(_ => returnType);
|
||||
try {
|
||||
const subs = unify(
|
||||
fnTypeInfo.type,
|
||||
fakeFnType);
|
||||
// console.log("subs:", prettySS(subs));
|
||||
let type, newEnv;
|
||||
if (subs.has(returnType!.symbol)) {
|
||||
type = subs.get(returnType!.symbol);
|
||||
subs.delete(returnType!.symbol);
|
||||
newEnv = inputTypeInfo.newEnv
|
||||
}
|
||||
else {
|
||||
type = returnType;
|
||||
newEnv = envWithReturn;
|
||||
}
|
||||
const mergedSubs = mergeSubstitutionsN([
|
||||
fnTypeInfo.subs,
|
||||
inputTypeInfo.subs,
|
||||
subs,
|
||||
]);
|
||||
return {
|
||||
type,
|
||||
subs: mergedSubs,
|
||||
newEnv,
|
||||
inputEnv,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof IncompatibleTypesError) {
|
||||
const [type, newEnv] = typeUnknown(env);
|
||||
return {
|
||||
type,
|
||||
subs: new Map(),
|
||||
newEnv,
|
||||
err: e,
|
||||
inputEnv,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function inferTypeLet(s: LetInBlockState, env: Environment): TypeInfoLambda {
|
||||
const valTypeInfo = inferType(s.value, env);
|
||||
const innerEnv = {
|
||||
names: trie.insert(env.names)(s.name)({kind: "value", t: valTypeInfo.type}),
|
||||
typevars: env.typevars,
|
||||
};
|
||||
return {
|
||||
...inferType(s.inner, innerEnv),
|
||||
paramType: valTypeInfo.type,
|
||||
innerEnv,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function inferTypeLambda(s: LambdaBlockState, env: Environment): TypeInfoLambda {
|
||||
let [paramType] = typeUnknown(env);
|
||||
const paramTypeVar = paramType.symbol;
|
||||
|
||||
let iterations = 1;
|
||||
while (true) {
|
||||
const innerEnv = {
|
||||
names: trie.insert(env.names)(s.paramName)({kind: "unknown", t: paramType}),
|
||||
typevars: env.typevars.union(occurring(paramType) as Set<string>),
|
||||
};
|
||||
const typeInfo = inferType(s.expr, innerEnv);
|
||||
const subsWithoutPType = new Map(typeInfo.subs);
|
||||
subsWithoutPType.delete(paramTypeVar);
|
||||
const inferredPType = substitute(paramType, typeInfo.subs, []);
|
||||
const inferredPType2 = rewriteInferredType(inferredPType, env);
|
||||
if (eqType(inferredPType2)(paramType)) {
|
||||
return {
|
||||
type: fnType(_ => paramType)(_ => typeInfo.type),
|
||||
subs: subsWithoutPType,
|
||||
newEnv: env, // no change
|
||||
paramType,
|
||||
innerEnv,
|
||||
};
|
||||
}
|
||||
if ((iterations++) == 4) {
|
||||
throw new Error("too many iterations!");
|
||||
}
|
||||
// console.log("-----------------", iterations);
|
||||
// console.log("paramType:", prettyT(paramType));
|
||||
// console.log("inferredPType:", prettyT(inferredPType));
|
||||
// console.log("inferredPType2:", prettyT(inferredPType2));
|
||||
// console.log("env:", [...env.typevars].map(getHumanReadableName));
|
||||
// console.log("innerEnv:", [...innerEnv.typevars].map(getHumanReadableName));
|
||||
// console.log("-----------------");
|
||||
paramType = inferredPType2;
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
const highestTypeVar2 = (typevars: Iterable<string>) => {
|
||||
let highest = -1;
|
||||
for (const typeVar of typevars) {
|
||||
highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar));
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
function rewriteType(type: Type, env: Environment): [Type, Environment] {
|
||||
const [recomputed] = recomputeTypeVars([type], highestTypeVar2(env.typevars)+1);
|
||||
return [type, {
|
||||
names: env.names,
|
||||
typevars: env.typevars.union(occurring(recomputed)),
|
||||
}];
|
||||
}
|
||||
function typeUnknown(env: Environment): [Type, Environment] {
|
||||
const type = TYPE_VARS[highestTypeVar2(env.typevars)+1];
|
||||
const newEnv = {
|
||||
names: env.names,
|
||||
typevars: new Set([...env.typevars, type.symbol]),
|
||||
};
|
||||
return [type, newEnv];
|
||||
}
|
||||
function rewriteInferredType(type: Type, env: Environment) {
|
||||
const substitutions = new Map();
|
||||
let i = 0;
|
||||
for (const o of occurring(type)) {
|
||||
while (env.typevars.has(UNBOUND_SYMBOLS[i])) {
|
||||
i++;
|
||||
}
|
||||
if (!env.typevars.has(o)) {
|
||||
substitutions.set(o, TYPE_VARS[i]);
|
||||
}
|
||||
}
|
||||
return substitute(type, substitutions, []);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue