move everything

This commit is contained in:
Joeri Exelmans 2025-05-23 22:35:47 +02:00
parent 3ff7e76694
commit 9050581a10
25 changed files with 37 additions and 42 deletions

635
src/eval/eval.ts Normal file
View 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
View 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, []);
}