From 5b6bcf5ffa0f56a3401080c906e7e53cc86ba819 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Mon, 19 May 2025 23:59:21 +0200 Subject: [PATCH] everything seems to work ... but code is dirty --- pnpm-lock.yaml | 8 +- src/App.tsx | 17 ++- src/CallBlock.tsx | 8 +- src/ExprBlock.tsx | 2 +- src/LambdaBlock.tsx | 3 +- src/LetInBlock.tsx | 6 +- src/configurations.ts | 8 +- src/eval.ts | 328 ++++++++++++++++++++++++++++-------------- tsconfig.app.json | 2 +- tsconfig.node.json | 2 +- 10 files changed, 253 insertions(+), 131 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82790c4..77d2341 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 5.2.5 dope2: specifier: git+https://deemz.org/git/joeri/dope2.git - version: git+https://deemz.org/git/joeri/dope2.git#4fcfea409a3961733f80f8dbb2810efb3d8f2cf4 + version: git+https://deemz.org/git/joeri/dope2.git#8cfbd6116ffe778efb02c37133a1ff633ae171df react: specifier: ^19.1.0 version: 19.1.0 @@ -597,8 +597,8 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dope2@git+https://deemz.org/git/joeri/dope2.git#4fcfea409a3961733f80f8dbb2810efb3d8f2cf4: - resolution: {commit: 4fcfea409a3961733f80f8dbb2810efb3d8f2cf4, repo: https://deemz.org/git/joeri/dope2.git, type: git} + dope2@git+https://deemz.org/git/joeri/dope2.git#8cfbd6116ffe778efb02c37133a1ff633ae171df: + resolution: {commit: 8cfbd6116ffe778efb02c37133a1ff633ae171df, repo: https://deemz.org/git/joeri/dope2.git, type: git} version: 0.0.1 esbuild@0.25.4: @@ -1431,7 +1431,7 @@ snapshots: deep-is@0.1.4: {} - dope2@git+https://deemz.org/git/joeri/dope2.git#4fcfea409a3961733f80f8dbb2810efb3d8f2cf4: + dope2@git+https://deemz.org/git/joeri/dope2.git#8cfbd6116ffe778efb02c37133a1ff633ae171df: dependencies: functional-red-black-tree: 1.0.1 diff --git a/src/App.tsx b/src/App.tsx index 9329df2..463b1de 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,7 @@ import './App.css'; import { GlobalContext } from './GlobalContext'; import { ExprBlock, type ExprBlockState } from './ExprBlock'; import { extendedEnv } from './EnvContext'; -import { biggerExample, higherOrder, initialEditorState, lambda2Params, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; +import { biggerExample, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations"; import { evalEditorBlock } from "./eval"; const commands: [string, string[], string][] = [ @@ -21,6 +21,9 @@ const examples: [string, ExprBlockState][] = [ ["bigger example" , biggerExample ], ["lambda 2 params" , lambda2Params ], ["higher order" , higherOrder ], + ["higher order 2" , higherOrder2Params ], + ["push Bool" , pushBool ], + ["inc" , inc ], ]; type AppState = { @@ -35,16 +38,16 @@ const defaultState = { function loadFromLocalStorage(): AppState { if (localStorage["appState"]) { - try { + // try { const appState = JSON.parse(localStorage["appState"]); // may throw // if our state is corrupt, discover it eagerly: - evalEditorBlock(appState.history.at(-1), extendedEnv); + // evalEditorBlock(appState.history.at(-1), extendedEnv); return appState; // all good - } - catch (e) { - console.log('error recovering state from localStorage (resetting):', e); - } + // } + // catch (e) { + // console.log('error recovering state from localStorage (resetting):', e); + // } } return defaultState; } diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index 6af73d2..aa7d859 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -28,7 +28,7 @@ function nestedFnProperties({state, setState, suggestionPriority}: CallBlockProp } const fnSuggestionPriority = (fnSuggestion: ResolvedType) => computePriority( fnSuggestion, - evalEditorBlock(state.input, env), + evalEditorBlock(state.input, env)[0], suggestionPriority, env, ); @@ -43,7 +43,7 @@ function nestedInputProperties({state, setState, suggestionPriority}: CallBlockP setState(state => state.fn); // we become our function } const inputSuggestionPriorirty = (inputSuggestion: ResolvedType) => computePriority( - evalEditorBlock(state.fn, env), // fn *may* be set + evalEditorBlock(state.fn, env)[0], // fn *may* be set inputSuggestion, // suggestions will be for input suggestionPriority, // priority function we get from parent block env, @@ -53,7 +53,7 @@ function nestedInputProperties({state, setState, suggestionPriority}: CallBlockP export function CallBlock(props: CallBlockProps) { const env = useContext(EnvContext); - const resolved = evalEditorBlock(props.state, env); + const [resolved] = evalEditorBlock(props.state, env); return
@@ -73,7 +73,7 @@ export function CallBlock(props: CallBlockProps) { } function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) { - const resolved = evalCallBlock2(fn, input, env); + const [resolved] = evalCallBlock2(fn, input, env); const score = scoreResolved(resolved, outPriority); return score; } diff --git a/src/ExprBlock.tsx b/src/ExprBlock.tsx index 83fc013..0b74baf 100644 --- a/src/ExprBlock.tsx +++ b/src/ExprBlock.tsx @@ -183,7 +183,7 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB />; } } - const resolved = evalEditorBlock(state, env); + const [resolved] = evalEditorBlock(state, env); return {renderBlock()}
diff --git a/src/LambdaBlock.tsx b/src/LambdaBlock.tsx index eb9592a..f7b9963 100644 --- a/src/LambdaBlock.tsx +++ b/src/LambdaBlock.tsx @@ -50,7 +50,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName); - const exprResolved = evalEditorBlock(state.expr, staticInnerEnv); + const [exprResolved] = evalEditorBlock(state.expr, staticInnerEnv); const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType; @@ -62,6 +62,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr unification: new Map(), // <- is this correct? }) + // const {exprResolved, env: newEnv} = computeLambdaBlockType(state.paramName, state.expr, env); return λ diff --git a/src/LetInBlock.tsx b/src/LetInBlock.tsx index fb02080..d604d79 100644 --- a/src/LetInBlock.tsx +++ b/src/LetInBlock.tsx @@ -41,7 +41,7 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority} const valueSuggestionPriority = (suggestion: ResolvedType) => { const innerEnv = makeInnerEnv(env, name, suggestion); - const resolved = evalEditorBlock(inner, innerEnv); + const [resolved] = evalEditorBlock(inner, innerEnv); return scoreResolved(resolved, suggestionPriority); }; @@ -51,7 +51,7 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority} }, []); useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]); - const valueResolved = evalEditorBlock(value, env); + const [valueResolved] = evalEditorBlock(value, env); const innerEnv = makeInnerEnv(env, name, valueResolved); return <> @@ -91,7 +91,7 @@ function InnerMost({state, setState, suggestionPriority}) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); - const valueResolved = evalEditorBlock(state.value, env); + const [valueResolved] = evalEditorBlock(state.value, env); const innerEnv = makeInnerEnv(env, state.name, valueResolved); const onCancel = () => setState(state => state.value); if (state.inner.kind === "let" && globalContext?.syntacticSugar) { diff --git a/src/configurations.ts b/src/configurations.ts index 4b3df89..17784eb 100644 --- a/src/configurations.ts +++ b/src/configurations.ts @@ -128,4 +128,10 @@ export const lambda2Params: ExprBlockState = { } }; -export const higherOrder: ExprBlockState = {"kind":"let","inner":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"myBinaryApply","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":true}},"name":"myBinaryApply","value":{"kind":"lambda","paramName":"x","expr":{"kind":"lambda","paramName":"y","expr":{"kind":"lambda","paramName":"fn","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"y","value":{"kind":"name"},"focus":true}}}}}}; \ No newline at end of file +export const higherOrder: ExprBlockState = {"kind":"let","inner":{"kind":"input","text":"","value":{"kind":"text"},"focus":false},"name":"myBinaryApply","value":{"kind":"lambda","paramName":"x","expr":{"kind":"lambda","paramName":"fn","expr":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}}}}}; + +export const higherOrder2Params: ExprBlockState = {"kind":"let","inner":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"myBinaryApply","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":true}},"name":"myBinaryApply","value":{"kind":"lambda","paramName":"x","expr":{"kind":"lambda","paramName":"y","expr":{"kind":"lambda","paramName":"fn","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"y","value":{"kind":"name"},"focus":true}}}}}}; + +export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"Bool","value":{"kind":"name"},"focus":true}}; + +export const inc: ExprBlockState = {"kind":"let","inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}}; \ No newline at end of file diff --git a/src/eval.ts b/src/eval.ts index c1af9f4..16ee0f7 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -1,4 +1,4 @@ -import { Double, fnType, getHumanReadableName, getSymbol, Int, mergeUnifications, NotAFunctionError, occurring, prettyT, prettyU, recomputeTypeVars, reduceUnification, substitute, symbolFunction, trie, TYPE_VARS, UNBOUND_SYMBOLS, UnifyError, unifyLL, transitivelyGrow } from "dope2"; +import { Double, fnType, getHumanReadableName, getSymbol, Int, mergeUnifications, NotAFunctionError, occurring, prettyT, prettyU, recomputeTypeVars, reduceUnification, substitute, symbolFunction, trie, TYPE_VARS, UNBOUND_SYMBOLS, UnifyError, unifyLL, transitivelyGrow, isTypeVar, set, compareTypes, recomputeTypeVarsWithInverse } from "dope2"; import type { ExprBlockState } from "./ExprBlock"; import type { InputValueType } from "./InputBlock"; @@ -43,7 +43,15 @@ export interface Unknown { // the value of every block is either known (Dynamic), an error, or unknown export type ResolvedType = Dynamic | DeepError | Unknown; -export const evalEditorBlock = (s: ExprBlockState, env): ResolvedType => { +// export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => { +// const [resolved] = proxyEditorBlock(s, env); +// const [t, newEnv] = recomputeTypeVarsForEnv(resolved.t, env); +// return [{...resolved, t }, newEnv]; +// }; + +class NotFoundError extends Error {} + +export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => { if (s.kind === "input") { return evalInputBlock(s.text, s.value, env); } @@ -58,121 +66,211 @@ export const evalEditorBlock = (s: ExprBlockState, env): ResolvedType => { } }; -export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType { +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; + if (found.kind === "unknown") { + console.log('returning', text, 'as-is'); + return [found, env]; // don't rewrite lambda parameters + } + console.log('rewriting', text); + return recomputeTypeVarsForEnv(text, found, env); } } // kind === "text" -> unresolved - return { + const [t, env2] = makeTypeVar(env, 'err') + return [{ kind: "error", - t: makeTypeVar(env, 'err')[0], - e: new Error(`'${text}' not found`), + t, + e: new NotFoundError(`'${text}' not found`), depth: 0, unification: new Map(), - }; + }, env2]; +} + +export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] { + const [fnResolved, env2] = evalEditorBlock(fn, env); + const [inputResolved, env3] = evalEditorBlock(input, env2); + console.log('==== evalCallBlock ===='); + console.log('env :', env); + console.log('fnResolved :', fnResolved); + console.log('env2 :', env2); + console.log('inputResolved:', inputResolved); + console.log('env3 :', env3); + console.log('======================='); + return evalCallBlock2(fnResolved, inputResolved, env3); +} + +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; } -export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env): ResolvedType { - if (getSymbol(fnResolved.t) !== symbolFunction) { - // worst outcome: we know nothing about the result! - return makeError(env, - new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`), - mergeUnifications(fnResolved.unification, inputResolved.unification), - ) - } +const inverseUnification = (uni, inverse) => { + return new Map([...uni] + .filter(([symbol]) => !inverse.has(inverse.get(symbol))) + .map(([symbol, types]) => [inverse.get(symbol) || symbol, types]) + ); +} + +function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, env: Environment): [ResolvedType,Environment] { + const [[newType], inverse] = recomputeTypeVarsWithInverse([resolved.t], env.nextFreeTypeVar); + 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), + typeVars, + nextFreeTypeVar: highestTypeVar2(typeVars) + 1, + }; + + return [newResolved, newEnv]; +} + +function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] { try { - // fn is a function... - const [rewrittenFnType] = recomputeTypeVars([fnResolved.t], env.nextFreeTypeVar); - const unification = (unifyLL(rewrittenFnType.params[0](rewrittenFnType), inputResolved.t)); - - const inputTypeVars = occurring(inputResolved.t); - const fnTypeVars = occurring(fnResolved.t); - const subsetOfUnification = new Map([...unification].filter(([typeVar]) => inputTypeVars.has(typeVar))); - const otherSubSetOfUnification = new Map([...unification].filter(([typeVar]) => fnTypeVars.has(typeVar))); - - const outType = substitute( - rewrittenFnType.params[1](rewrittenFnType), - reduceUnification(unification), - []); // <- not important - - const grandUnification = [fnResolved.unification, inputResolved.unification] - .reduce(mergeUnifications, unification); + // turn input in to a function + const [abstractOutputType, env2] = makeTypeVar(env, ""); + const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType); if (IS_DEV) { - console.log('========= evalCallBlock2 =========') + console.log('========= evalCallBlock3 =========') + console.log('env :', env); + console.log('fnKind :', fnResolved.kind); + console.log('inputKind :', inputResolved.kind); console.log('fnType :', prettyT(fnResolved.t)); - console.log('rewrittenFnType :', prettyT(rewrittenFnType)); console.log('inputType :', prettyT(inputResolved.t)); + console.log('matchFnType :', prettyT(matchFnType)); + } + + // unify both functions + const unification = /*transitivelyGrow*/(unifyLL(fnResolved.t, matchFnType)); + + const unificationR = reduceUnification(unification); + const unifiedFnType = substitute( + // matchFnType, + fnResolved.t, + unificationR, []); + + const outType = unifiedFnType.params[1](unifiedFnType); + + const newEnv = (outType === abstractOutputType) ? env2 : 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 (IS_DEV) { console.log('unification :', prettyU(unification)); - console.log('subsetOfUnification :', prettyU(subsetOfUnification)); - console.log('otherSubSetOfUnification:', prettyU(otherSubSetOfUnification)); + console.log('unificationInvR :', prettyRU(unificationR)); + console.log('unifiedFnType :', prettyT(unifiedFnType)); console.log('outType :', prettyT(outType)); - // console.log('inputTypeVars :', `{${[...inputTypeVars].map(getHumanReadableName).join(', ')}}`); - // console.log('fnTypeVars :', `{${[...fnTypeVars].map(getHumanReadableName).join(', ')}}`); console.log('fn.unification :', prettyU(fnResolved.unification)); console.log('input.unification :', prettyU(inputResolved.unification)); + console.log('unificationWithoutOutType:', prettyU(unificationWithoutOutType)); + } + + const grandUnification = [fnResolved.unification, inputResolved.unification] + .reduce(mergeUnifications, unificationWithoutOutType); + + // const grandUnification = unificationWithoutOutType; + + if (IS_DEV) { console.log('grandUnification :', prettyU(grandUnification)); console.log('==================================') } - if (inputResolved.kind === "error") { - return { + // throw inputResolved.e; + return [{ kind: "error", e: inputResolved.e, // bubble up the error depth: 0, t: outType, unification: grandUnification, - }; + }, newEnv]; } if (fnResolved.kind === "error") { + // throw fnResolved.e; // also bubble up - return { + return [{ kind: "error", e: fnResolved.e, depth: fnResolved.depth+1, t: outType, unification: grandUnification, - }; + }, newEnv]; } // if the above statement did not throw => types are compatible... if (inputResolved.kind === "value" && fnResolved.kind === "value") { const outValue = fnResolved.i(inputResolved.i); - return { + console.log('outValue:', outValue); + return [{ kind: "value", i: outValue, t: outType, unification: grandUnification, - }; + }, newEnv]; } else { // we don't know the value, but we do know the type: - return { + return [{ kind: "unknown", t: outType, unification: grandUnification, - }; + }, newEnv]; } } catch (e) { - if ((e instanceof UnifyError)) { + // if ((e instanceof UnifyError)) { + console.log('UnifyError!', (e as Error).message); // even though fn was incompatible with the given parameter, we can still suppose that our output-type will be that of fn...? const outType = fnResolved.t.params[1](fnResolved.t); try { - return { + return [{ kind: "error", - e, + e: e as Error, depth: 0, t: outType, unification: mergeUnifications(fnResolved.unification, inputResolved.unification), // may throw! - }; + }, env]; } catch (e) { if ((e instanceof UnifyError)) { @@ -180,46 +278,43 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved } throw e; } - } - throw e; + // } + // throw e; } -}; - -export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env): ResolvedType { - const fnResolved = evalEditorBlock(fn, env); - const inputResolved = evalEditorBlock(input, env); - return evalCallBlock2(fnResolved, inputResolved, env); } -export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env): ResolvedType { - const valueResolved = evalEditorBlock(value, env); +export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] { + const [valueResolved] = evalEditorBlock(value, env); const innerEnv = makeInnerEnv(env, name, valueResolved); return evalEditorBlock(inner, innerEnv); } +const prettyRU = (rUni: Map) => { + return '{'+[...rUni].map(([symbol,type]) => `${getHumanReadableName(symbol)} => ${prettyT(type)}`).join(', ')+'}'; +} -export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): ResolvedType { +export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment] { const [paramType, staticInnerEnv] = makeTypeVar(env, paramName); - const exprResolved = evalEditorBlock(expr, staticInnerEnv); - 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 lambdaTSubstituted = substitute( - lambdaT, - reduceUnification(exprResolved.unification), - []); // <- not important if (IS_DEV) { - console.log('========= evalLambdaBlock =========') - console.log('paramType :', prettyT(paramType)); - console.log('exprType :', prettyT(exprResolved.t)); - console.log('lambdaType :', prettyT(lambdaT)); - console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted)); + console.log('====== begin evalLambdaBlock ======') + console.log('paramName :', paramName); + console.log('paramType :', prettyT(paramType)); + console.log('staticInnerEnv:', staticInnerEnv); console.log('===================================') } - // console.log('inner kind', exprResolved.kind, paramName); - if (exprResolved.kind === "error") { - return { + const [exprResolved] = evalEditorBlock(expr, staticInnerEnv); + 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, @@ -227,25 +322,41 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R unification: exprResolved.unification, } } - const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted); - const fn = (x: any) => { - const innerEnv = makeInnerEnv(env, paramName, { + else { + const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted); + lambdaResolved = { kind: "value", - i: x, - t: paramTypeSubstituted, - unification: new Map(), - }); - const result = evalEditorBlock(expr, innerEnv); - if (result.kind === "value") { - return result.i; + t: lambdaTSubstituted, + i: (x: any) => { + const innerEnv = makeInnerEnv(env, paramName, { + kind: "value", + i: x, + t: paramTypeSubstituted, + unification: new Map(), + }); + const [result] = evalEditorBlock(expr, innerEnv); + if (result.kind === "value") { + return result.i; + } + }, + unification: exprResolved.unification, } } - return { - kind: "value", - t: lambdaTSubstituted, - i: fn, - unification: exprResolved.unification, - }; + + // const [lambdaResolvedNormalized, resultEnv] = recomputeTypeVarsForEnv(paramName, lambdaResolved, env); + + if (IS_DEV) { + console.log('======= end evalLambdaBlock =======') + console.log('paramType :', prettyT(paramType)); + console.log('exprType :', prettyT(exprResolved.t)); + console.log('exprUnification :', prettyU(exprResolved.unification)); + console.log('exprUnificationR :', prettyRU(reduced)); + console.log('lambdaType :', prettyT(lambdaT)); + console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted)); + // console.log('normalizedT :', prettyT(lambdaResolvedNormalized.t)); + console.log('===================================') + } + return [lambdaResolved, env]; } export function haveValue(resolved: ResolvedType) { @@ -253,7 +364,7 @@ export function haveValue(resolved: ResolvedType) { return resolved.kind === "value"; } -function parseLiteral(text: string, type: string, env): ResolvedType { +function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] { // dirty if (type === "Int") { return parseAsInt(text, env); @@ -264,29 +375,29 @@ function parseLiteral(text: string, type: string, env): ResolvedType { return makeError(env, new Error("Failed to parse")); } -function parseAsDouble(text: string, env): ResolvedType { +function parseAsDouble(text: string, env: Environment): [ResolvedType,Environment] { if (text !== '') { const num = Number(text); if (!Number.isNaN(num)) { - return { + 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): ResolvedType { +function parseAsInt(text: string, env: Environment): [ResolvedType,Environment] { if (text !== '') { try { - return { + return [{ kind: "value", i: BigInt(text), t: Int, unification: new Map(), - }; // may throw + }, env]; // may throw } catch {} } @@ -295,9 +406,10 @@ function parseAsInt(text: string, env): ResolvedType { const literalParsers = [parseAsDouble, parseAsInt]; -export function attemptParseLiteral(text: string, env): Dynamic[] { +export function attemptParseLiteral(text: string, env: Environment): Dynamic[] { return literalParsers.map(parseFn => parseFn(text, env)) - .filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[]; + .map(([resolved]) => resolved) + .filter((resolved) => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[]; } export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) { @@ -343,7 +455,7 @@ export function makeTypeVar(env: Environment, name: string): [Type, Environment] }]; } -function makeError(env: Environment, e: Error, unification: Unification=new Map()): DeepError { +function makeError(env: Environment, e: Error, unification: Unification=new Map()): [DeepError, Environment] { const idx = env.nextFreeTypeVar; const typeVar = TYPE_VARS[idx]; const deepError: DeepError = { @@ -353,9 +465,9 @@ function makeError(env: Environment, e: Error, unification: Unification=new Map( e, depth: 0, }; - return deepError; - // , { - // names: trie.insert(env.names)('err')(deepError), - // nextFreeTypeVar: idx + 1, - // }]; + return [deepError, { + names: env.names, + nextFreeTypeVar: idx + 1, + typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]), + }]; } \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 1d6ea11..9245011 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -3,7 +3,7 @@ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2023", "useDefineForClassFields": true, - "lib": ["ES2023", "DOM", "DOM.Iterable"], + "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, diff --git a/tsconfig.node.json b/tsconfig.node.json index 5326fd6..de22961 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -2,7 +2,7 @@ "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "target": "ES2023", - "lib": ["ES2023"], + "lib": ["ESNext"], "module": "ESNext", "skipLibCheck": true,