From 32bdc23ea7899ba72b21b3dbbfdbe7f9913b2136 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Mon, 26 May 2025 15:34:50 +0200 Subject: [PATCH] wrote interpreter (independent of type inferencer) --- src/component/app/App.tsx | 2 + src/component/expr/CallBlock.tsx | 6 +- src/component/expr/InputBlock.tsx | 16 +- src/context/EnvContext.ts | 4 +- src/eval/eval.ts | 652 ++++-------------------------- src/eval/infer_type.ts | 26 +- 6 files changed, 104 insertions(+), 602 deletions(-) diff --git a/src/component/app/App.tsx b/src/component/app/App.tsx index b91fd19..e49c3f5 100644 --- a/src/component/app/App.tsx +++ b/src/component/app/App.tsx @@ -7,6 +7,7 @@ import { actionShortcuts } from './actions'; import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, setOfListOfBool, tripleFunctionCallEditorState } from "./configurations"; import './App.css'; +import { evalExpr } from '../../eval/eval'; const examples: [string, ExprBlockState][] = [ @@ -181,6 +182,7 @@ export function App() { }} typeInfo={typeInfo} /> + ={evalExpr(currentState, extendedEnv)} diff --git a/src/component/expr/CallBlock.tsx b/src/component/expr/CallBlock.tsx index 7d89673..6dfda9d 100644 --- a/src/component/expr/CallBlock.tsx +++ b/src/component/expr/CallBlock.tsx @@ -3,7 +3,7 @@ import { useContext } from "react"; import { CallContext } from "../../context/CallContext"; import { EnvContext } from "../../context/EnvContext"; import { GlobalContext } from "../../context/GlobalContext"; -import { type Environment, type TypeInfoCall } from "../../eval/infer_type"; +import { type StaticEnvironment, type TypeInfoCall } from "../../eval/infer_type"; import { getActions } from "../app/actions"; import { Type } from "../other/Type"; import "./CallBlock.css"; @@ -22,7 +22,7 @@ export interface CallBlockProps< typeInfo: TypeInfoCall; } -function nestedFnProperties({state, setState, score, typeInfo}: CallBlockProps, env: Environment) { +function nestedFnProperties({state, setState, score, typeInfo}: CallBlockProps, env: StaticEnvironment) { const setFn = (callback: SetStateFn) => { setState(state => ({...state, fn: callback(state.fn)})); }; @@ -35,7 +35,7 @@ function nestedFnProperties({state, setState, score, typeInfo}: CallBlockProps, return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn, typeInfo: typeInfo.fn}; } -function nestedInputProperties({state, setState, score, typeInfo}: CallBlockProps, env: Environment) { +function nestedInputProperties({state, setState, score, typeInfo}: CallBlockProps, env: StaticEnvironment) { const setInput = (callback: SetStateFn) => { setState(state => ({...state, input: callback(state.input)})); }; diff --git a/src/component/expr/InputBlock.tsx b/src/component/expr/InputBlock.tsx index 6b80e45..d5c5437 100644 --- a/src/component/expr/InputBlock.tsx +++ b/src/component/expr/InputBlock.tsx @@ -5,7 +5,7 @@ import { trie } from "dope2"; import { CallContext } from "../../context/CallContext"; import { EnvContext } from "../../context/EnvContext"; import { GlobalContext } from "../../context/GlobalContext"; -import { inferTypeInput, type Environment, type Type, type TypeInfoInput } from "../../eval/infer_type"; +import { inferTypeInput, type StaticEnvironment, type Type, type TypeInfoInput } from "../../eval/infer_type"; import { getActions } from "../app/actions"; import { Input } from "../other/Input"; import { Type as TypeBlock } from "../other/Type"; @@ -46,9 +46,11 @@ const attemptLiterals = [ const computeSuggestions = ( text: string, - env: Environment, + env: StaticEnvironment, score: InputBlockProps['score'], ): PrioritizedSuggestionType[] => { + const startTime = performance.now(); + const ls = [ ...attemptLiterals .filter(([_, test]) => test(text)) @@ -69,9 +71,17 @@ const computeSuggestions = ( }, })), ]; + const midTime = performance.now(); + // return []; // <-- uncomment to disable suggestions (useful for debugging) - return ls.map((state) => [score(state), inferTypeInput(state, env).type, state] as PrioritizedSuggestionType) + const result = ls.map((state) => [score(state), inferTypeInput(state, env).type, state] as PrioritizedSuggestionType) .sort(([a],[b]) => b-a); + + const endTime = performance.now(); + + console.log(`computing suggestions took ${midTime-startTime} + ${endTime - midTime}`); + + return result; } export function InputBlock({ state, setState, score, onCancel, typeInfo }: InputBlockProps) { diff --git a/src/context/EnvContext.ts b/src/context/EnvContext.ts index fd3dc15..ce7a578 100644 --- a/src/context/EnvContext.ts +++ b/src/context/EnvContext.ts @@ -1,14 +1,14 @@ import { createContext } from "react"; import { getDefaultTypeParser, module2Env, ModuleStd } from "dope2"; // import type { Dynamic, Environment } from "./eval"; -import type { Environment } from "../eval/infer_type"; +import type { StaticEnvironment } from "../eval/infer_type"; export const functionWith3Params = i => j => k => i+j+k; export const functionWith4Params = i => j => k => l => i+j+k+l; const mkType = getDefaultTypeParser(); -export const extendedEnv: Environment = { +export const extendedEnv: StaticEnvironment = { names: module2Env(ModuleStd.concat([ ["true", {i: true, t: mkType("Bool")}], ["false", {i: false, t: mkType("Bool")}], diff --git a/src/eval/eval.ts b/src/eval/eval.ts index 972f2af..df2c7e7 100644 --- a/src/eval/eval.ts +++ b/src/eval/eval.ts @@ -1,588 +1,78 @@ -// import { Double, eqType, fnType, getHumanReadableName, getSymbol, Int, isTypeVar, occurring, prettyT, recomputeTypeVarsWithInverse, substitute, symbolFunction, transitivelyGrow, trie, TYPE_VARS, UNBOUND_SYMBOLS } from "dope2"; +import { trie } 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"; -// import type { ExprBlockState } from "./ExprBlock"; -// import type { InputValueType } from "./InputBlock"; +export interface DynamicEnvironment { + names: any; +} -// const IS_DEV = (import.meta.env.MODE === "development"); -// const VERBOSE = true && IS_DEV; +export type EvalResult = any; -// export interface Environment { -// names: any; -// nextFreeTypeVar: number; -// typeVars: Set; -// } +export function evalExpr(s: ExprBlockState, env: DynamicEnvironment): EvalResult { + if (s.kind === "input") { + return evalInput(s, env); + } + else if (s.kind === "call") { + return evalCall(s, env); + } + else if (s.kind === "let") { + return evalLet(s, env); + } + else { // (s.kind === "lambda") + return evalLambda(s, env); + } +} -// interface Type { -// symbol: string; -// params: any[]; -// }; +export function evalInput(s: InputBlockState, env: DynamicEnvironment): EvalResult { + if (s.value.kind === "literal") { + const ctor = { + Int: BigInt, + Double: Number, + }[s.value.type] as (s: string) => any; + return ctor(s.text); + } + else if (s.value.kind === "name") { + const found = trie.get(env.names)(s.text); + if (found) { + if (found.recursive) { + return found.i(); + } + return found.i; + } + } +} -// type Unification = Map; +export function evalCall(s: CallBlockState, env: DynamicEnvironment): EvalResult { + const fn = evalExpr(s.fn, env); + const input = evalExpr(s.input, env); + if (fn !== undefined && input !== undefined) { + return fn(input); + } +} -// export interface DeepError { -// kind: "error"; -// e: Error; -// depth: number; -// t: Type; -// unification: Unification; -// } +export function evalLet(s: LetInBlockState, env: DynamicEnvironment): EvalResult { + const valueEnv = { + names: trie.insert(env.names)(s.name)({ + recursive: true, + i: () => value, + }), + }; + const value = evalExpr(s.value, valueEnv); + const innerEnv = { + names: trie.insert(env.names)(s.name)({i: value}), + } + return evalExpr(s.inner, innerEnv); +} -// // 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("", 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("", 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; -// 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, ""); -// 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) => { -// 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("", 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!"); -// } -// } -// } -// } - - -// 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), -// } -// } -// } +export function evalLambda(s: LambdaBlockState, env: DynamicEnvironment): EvalResult { + const fn = x => { + const innerEnv = { + names: trie.insert(env.names)(s.paramName)({i: x}) + }; + return evalExpr(s.expr, innerEnv); + }; + return fn; +} diff --git a/src/eval/infer_type.ts b/src/eval/infer_type.ts index 41513ee..e7d8a8e 100644 --- a/src/eval/infer_type.ts +++ b/src/eval/infer_type.ts @@ -7,7 +7,7 @@ import type { LambdaBlockState } from "../component/expr/LambdaBlock"; import type { LetInBlockState } from "../component/expr/LetInBlock"; import { memoize } from "../util/memoize"; -export interface Environment { +export interface StaticEnvironment { names: any; typevars: Set; } @@ -22,7 +22,7 @@ export type Substitutions = Map; interface TypeInfoCommon { type: Type; subs: Substitutions; - newEnv: Environment; + newEnv: StaticEnvironment; err?: IncompatibleTypesError; } @@ -36,7 +36,7 @@ export interface TypeInfoCall extends TypeInfoCommon { } export interface TypeInfoLet extends TypeInfoCommon { kind: "let"; - innerEnv: Environment; + innerEnv: StaticEnvironment; value: TypeInfo; inner: TypeInfo; } @@ -44,12 +44,12 @@ export interface TypeInfoLambda extends TypeInfoCommon { kind: "lambda"; paramType: Type; inner: TypeInfo; - innerEnv: Environment; + innerEnv: StaticEnvironment; } export type TypeInfo = TypeInfoInput | TypeInfoCall | TypeInfoLet | TypeInfoLambda; -export const inferType = memoize(function inferType(s: ExprBlockState, env: Environment): TypeInfo { +export const inferType = memoize(function inferType(s: ExprBlockState, env: StaticEnvironment): TypeInfo { if (s.kind === "input") { return inferTypeInput(s, env); } @@ -64,7 +64,7 @@ export const inferType = memoize(function inferType(s: ExprBlockState, env: Envi } }); -export const inferTypeInput = memoize(function inferTypeInput(s: InputBlockState, env: Environment): TypeInfoInput { +export const inferTypeInput = memoize(function inferTypeInput(s: InputBlockState, env: StaticEnvironment): TypeInfoInput { if (s.value.kind === "literal") { const type = { Int: Int, @@ -102,7 +102,7 @@ export const inferTypeInput = memoize(function inferTypeInput(s: InputBlockState } }); -export const inferTypeCall = memoize(function inferTypeCall(s: CallBlockState, env: Environment): TypeInfoCall { +export const inferTypeCall = memoize(function inferTypeCall(s: CallBlockState, env: StaticEnvironment): TypeInfoCall { const fnTypeInfo = inferType(s.fn, env); const inputEnv = fnTypeInfo.newEnv; const inputTypeInfo = inferType(s.input, inputEnv); @@ -162,7 +162,7 @@ export const inferTypeCall = memoize(function inferTypeCall(s: CallBlockState, e } }); -export const inferTypeLet = memoize(function inferTypeLet(s: LetInBlockState, env: Environment): TypeInfoLet { +export const inferTypeLet = memoize(function inferTypeLet(s: LetInBlockState, env: StaticEnvironment): TypeInfoLet { const recursiveTypeInfo = iterateRecursiveType(s.name, s.value, env); // to eval the 'inner' expr, we only need to add our parameter to the environment: const innerEnv = { @@ -181,7 +181,7 @@ export const inferTypeLet = memoize(function inferTypeLet(s: LetInBlockState, en }; }); -export const inferTypeLambda = memoize(function inferTypeLambda(s: LambdaBlockState, env: Environment): TypeInfoLambda { +export const inferTypeLambda = memoize(function inferTypeLambda(s: LambdaBlockState, env: StaticEnvironment): TypeInfoLambda { const recursiveTypeInfo = iterateRecursiveType(s.paramName, s.expr, env); return { kind: "lambda", @@ -192,7 +192,7 @@ export const inferTypeLambda = memoize(function inferTypeLambda(s: LambdaBlockSt // Given a named value whose type we know nothing about, and an expression that computes the value (which may recursively contain the value), compute the type of the value. // Why? Both lambda functions and let-expressions can refer to themselves recursively. To infer their type, we need to recompute the type and feed it back to itself until some fixed point is reached. -function iterateRecursiveType(paramName: string, expr: ExprBlockState, env: Environment) { +function iterateRecursiveType(paramName: string, expr: ExprBlockState, env: StaticEnvironment) { let [paramType] = typeUnknown(env); const paramTypeVar = paramType.symbol; @@ -238,7 +238,7 @@ const highestTypeVar2 = (typevars: Iterable) => { } return highest; } -function rewriteType(type: Type, env: Environment): [Type, Environment] { +function rewriteType(type: Type, env: StaticEnvironment): [Type, StaticEnvironment] { const [recomputed] = recomputeTypeVars([type], highestTypeVar2(env.typevars)+1); const newTypeVars = occurring(recomputed); return [recomputed, { @@ -246,7 +246,7 @@ function rewriteType(type: Type, env: Environment): [Type, Environment] { typevars: env.typevars.union(newTypeVars), }]; } -function typeUnknown(env: Environment): [Type, Environment] { +function typeUnknown(env: StaticEnvironment): [Type, StaticEnvironment] { const type = TYPE_VARS[highestTypeVar2(env.typevars)+1]; const newEnv = { names: env.names, @@ -254,7 +254,7 @@ function typeUnknown(env: Environment): [Type, Environment] { }; return [type, newEnv]; } -function rewriteInferredType(type: Type, env: Environment): [Type, Environment] { +function rewriteInferredType(type: Type, env: StaticEnvironment): [Type, StaticEnvironment] { const substitutions = new Map(); const newTypeVars = new Set(env.typevars); let i = 0;