fix bug in type inferencing
This commit is contained in:
parent
bb6e742f5f
commit
c3f7cea310
8 changed files with 205 additions and 155 deletions
|
|
@ -2,10 +2,9 @@ import { useEffect, useState } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { ExprBlock, type ExprBlockState } from './ExprBlock';
|
import { ExprBlock, type ExprBlockState } from './ExprBlock';
|
||||||
import { GlobalContext } from './GlobalContext';
|
import { GlobalContext } from './GlobalContext';
|
||||||
import { biggerExample, emptySet, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
|
import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
|
||||||
import { actionShortcuts } from './actions';
|
import { actionShortcuts } from './actions';
|
||||||
import { scoreResolved, type ResolvedType } from './eval';
|
import { scoreResolved, type ResolvedType } from './eval';
|
||||||
import type { InputBlockState } from './InputBlock';
|
|
||||||
|
|
||||||
|
|
||||||
const examples: [string, ExprBlockState][] = [
|
const examples: [string, ExprBlockState][] = [
|
||||||
|
|
@ -19,6 +18,7 @@ const examples: [string, ExprBlockState][] = [
|
||||||
["push Bool" , pushBool ],
|
["push Bool" , pushBool ],
|
||||||
["inc" , inc ],
|
["inc" , inc ],
|
||||||
["empty set" , emptySet ],
|
["empty set" , emptySet ],
|
||||||
|
["factorial" , factorial ],
|
||||||
];
|
];
|
||||||
|
|
||||||
type AppState = {
|
type AppState = {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ const mkType = getDefaultTypeParser();
|
||||||
|
|
||||||
export const extendedEnv: Environment = {
|
export const extendedEnv: Environment = {
|
||||||
names: module2Env(ModuleStd.concat([
|
names: module2Env(ModuleStd.concat([
|
||||||
|
["true", {i: true, t: mkType("Bool")}],
|
||||||
|
["false", {i: false, t: mkType("Bool")}],
|
||||||
|
["leqZero", {i: n => leq => otherwise => (n<=0)?leq({}):otherwise({}), t: mkType("Int -> (Unit -> a) -> (Unit -> a) -> a")}],
|
||||||
["functionWith3Params", { i: functionWith3Params, t: mkType("Int->Int->Int->Int") }],
|
["functionWith3Params", { i: functionWith3Params, t: mkType("Int->Int->Int->Int") }],
|
||||||
["functionWith4Params", { i: functionWith4Params, t: mkType("Int->Int->Int->Int->Int")}]
|
["functionWith4Params", { i: functionWith4Params, t: mkType("Int->Int->Int->Int->Int")}]
|
||||||
]).map(([name, {i,t}]) => [name, { kind: "value", i, t, unification: new Map() }] as [string, Dynamic])
|
]).map(([name, {i,t}]) => [name, { kind: "value", i, t, unification: new Map() }] as [string, Dynamic])
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,29 @@
|
||||||
|
|
||||||
.typeSignature {
|
.typeSignature {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
z-index: 1;
|
/* z-index: 1; */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSignature.gotDebug {
|
||||||
|
background-color: gold;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeDebug {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSignature:hover > .typeDebug {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
white-space-collapse: preserve;
|
||||||
|
width: max-content;
|
||||||
|
background-color: #d2ebf1e0;
|
||||||
|
color: black;
|
||||||
|
font-family: var(--my-monospace-font);
|
||||||
|
padding: 4px;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor:hover > .typeSignature {
|
.editor:hover > .typeSignature {
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,11 @@ export function ExprBlock(props: ExprBlockProps) {
|
||||||
|
|
||||||
return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
|
return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
|
||||||
{renderBlock[props.state.kind]()}
|
{renderBlock[props.state.kind]()}
|
||||||
<div className="typeSignature">
|
{/* @ts-ignore */}
|
||||||
|
<div className={"typeSignature" + (resolved.__debug ? ' gotDebug' : '')}>
|
||||||
:: <Type type={getType(resolved)} />
|
:: <Type type={getType(resolved)} />
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
{resolved.__debug && <div className="typeDebug">{resolved.__debug}</div>}
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
placeholder="<c>"
|
placeholder="<c>"
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ export function Input({placeholder, text, suggestion, onTextChange, onEnter, onC
|
||||||
Backspace: () => {
|
Backspace: () => {
|
||||||
if (text.length === 0) {
|
if (text.length === 0) {
|
||||||
onCancel();
|
onCancel();
|
||||||
|
focusPrevElement();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { eqType, getSymbol, reduceUnification } from "dope2";
|
||||||
|
|
||||||
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
|
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import { evalExprBlock, makeInnerEnv, makeTypeVar } from "./eval";
|
import { evalExprBlock, evalLambdaBlock, makeInnerEnv, makeTypeVar } from "./eval";
|
||||||
|
|
||||||
import "./LambdaBlock.css";
|
import "./LambdaBlock.css";
|
||||||
import { Type } from "./Type";
|
import { Type } from "./Type";
|
||||||
|
|
@ -36,19 +36,9 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
|
||||||
expr: callback(state.expr),
|
expr: callback(state.expr),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName);
|
const [lambdaResolved, _, innerEnv] = evalLambdaBlock(state.paramName, state.expr, env);
|
||||||
|
|
||||||
const [exprResolved] = evalExprBlock(state.expr, staticInnerEnv);
|
const inferredParamType = lambdaResolved.t.params[0](lambdaResolved.t);
|
||||||
|
|
||||||
const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType;
|
|
||||||
|
|
||||||
const betterInnerEnv = eqType(paramType)(inferredParamType)
|
|
||||||
? staticInnerEnv
|
|
||||||
: makeInnerEnv(env, state.paramName, {
|
|
||||||
kind: "unknown",
|
|
||||||
t: inferredParamType,
|
|
||||||
unification: new Map(), // <- is this correct?
|
|
||||||
});
|
|
||||||
|
|
||||||
return <span className="lambdaBlock">
|
return <span className="lambdaBlock">
|
||||||
<span className="keyword">λ</span>
|
<span className="keyword">λ</span>
|
||||||
|
|
@ -71,7 +61,7 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
|
||||||
<span className="keyword">:</span>
|
<span className="keyword">:</span>
|
||||||
|
|
||||||
<div className="lambdaInner">
|
<div className="lambdaInner">
|
||||||
<EnvContext value={betterInnerEnv}>
|
<EnvContext value={innerEnv}>
|
||||||
<ExprBlock
|
<ExprBlock
|
||||||
state={state.expr}
|
state={state.expr}
|
||||||
setState={setExpr}
|
setState={setExpr}
|
||||||
|
|
|
||||||
|
|
@ -140,3 +140,5 @@ export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":
|
||||||
export const inc: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","focus":false,"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}}}};
|
export const inc: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","focus":false,"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}}}};
|
||||||
|
|
||||||
export const emptySet: ExprBlockState = {"kind":"call","fn":{"kind":"input","text":"set.emptySet","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"","value":{"kind":"text"},"focus":true}};
|
export const emptySet: ExprBlockState = {"kind":"call","fn":{"kind":"input","text":"set.emptySet","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"","value":{"kind":"text"},"focus":true}};
|
||||||
|
|
||||||
|
export const factorial: ExprBlockState = {"kind":"lambda","paramName":"factorial","focus":true,"expr":{"kind":"lambda","paramName":"n","focus":true,"expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"leqZero","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":false}},"input":{"kind":"lambda","paramName":"_","focus":false,"expr":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"lambda","paramName":"_","focus":false,"expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"mulInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":true}},"input":{"kind":"call","fn":{"kind":"input","text":"factorial","value":{"kind":"name"},"focus":true},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"-1","value":{"kind":"literal","type":"Int"},"focus":false}}}}}}}};
|
||||||
|
|
|
||||||
223
src/eval.ts
223
src/eval.ts
|
|
@ -1,10 +1,10 @@
|
||||||
import { Double, fnType, getHumanReadableName, getSymbol, Int, isTypeVar, mergeUnifications, NotAFunctionError, occurring, prettyT, prettyU, recomputeTypeVarsWithInverse, reduceUnification, substitute, symbolFunction, trie, TYPE_VARS, UNBOUND_SYMBOLS, UnifyError, unifyLL } from "dope2";
|
import { Double, eqType, fnType, getHumanReadableName, getSymbol, Int, isTypeVar, mergeUnifications, NotAFunctionError, occurring, prettyT, prettyU, recomputeTypeVarsWithInverse, reduceUnification, substitute, symbolFunction, transitivelyGrow, trie, TYPE_VARS, UNBOUND_SYMBOLS, UnifyError, unifyLL } from "dope2";
|
||||||
|
|
||||||
import type { ExprBlockState } from "./ExprBlock";
|
import type { ExprBlockState } from "./ExprBlock";
|
||||||
import type { InputValueType } from "./InputBlock";
|
import type { InputValueType } from "./InputBlock";
|
||||||
|
|
||||||
const IS_DEV = (import.meta.env.MODE === "development");
|
const IS_DEV = (import.meta.env.MODE === "development");
|
||||||
const VERBOSE = true;
|
const VERBOSE = true && IS_DEV;
|
||||||
|
|
||||||
export interface Environment {
|
export interface Environment {
|
||||||
names: any;
|
names: any;
|
||||||
|
|
@ -57,7 +57,8 @@ export const evalExprBlock = (s: ExprBlockState, env: Environment): [ResolvedTyp
|
||||||
return evalLetInBlock(s.value, s.name, s.inner, env);
|
return evalLetInBlock(s.value, s.name, s.inner, env);
|
||||||
}
|
}
|
||||||
else { // (s.kind === "lambda")
|
else { // (s.kind === "lambda")
|
||||||
return evalLambdaBlock(s.paramName, s.expr, env);
|
const [resolved, env2] = evalLambdaBlock(s.paramName, s.expr, env);
|
||||||
|
return [resolved, env2];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -68,15 +69,9 @@ export function evalInputBlock(text: string, value: InputValueType, env: Environ
|
||||||
else if (value.kind === "name") {
|
else if (value.kind === "name") {
|
||||||
const found = trie.get(env.names)(text);
|
const found = trie.get(env.names)(text);
|
||||||
if (found) {
|
if (found) {
|
||||||
if (found.kind === "unknown") {
|
|
||||||
// console.log('returning', text, 'as-is');
|
|
||||||
return [found, env]; // don't rewrite lambda parameters
|
return [found, env]; // don't rewrite lambda parameters
|
||||||
}
|
}
|
||||||
// console.log('rewriting', text);
|
|
||||||
return recomputeTypeVarsForEnv(text, found, env);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// kind === "text" -> unresolved
|
|
||||||
const [t, env2] = makeTypeVar(env, 'err')
|
const [t, env2] = makeTypeVar(env, 'err')
|
||||||
return [{
|
return [{
|
||||||
kind: "error",
|
kind: "error",
|
||||||
|
|
@ -88,24 +83,35 @@ export function evalInputBlock(text: string, value: InputValueType, env: Environ
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||||
const [fnResolved, env2] = evalExprBlock(fn, env);
|
let [fnResolved, env2] = evalExprBlock(fn, env);
|
||||||
const [inputResolved, env3] = evalExprBlock(input, env2);
|
if (fnResolved.kind === "value") {
|
||||||
if (VERBOSE) {
|
[fnResolved, env2] = recomputeTypeVarsForEnv("<name unused>", fnResolved, 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);
|
let [inputResolved, env3] = evalExprBlock(input, env2);
|
||||||
|
if (inputResolved.kind === "value") {
|
||||||
|
[inputResolved, env3] = recomputeTypeVarsForEnv("<name unused>", inputResolved, env3);
|
||||||
|
}
|
||||||
|
const result = evalCallBlock2(fnResolved, inputResolved, env3);
|
||||||
|
if (VERBOSE) {
|
||||||
|
// @ts-ignore
|
||||||
|
result[0].__debug = (result[0].__debug || '') + `
|
||||||
|
==== evalCallBlock ====
|
||||||
|
env.typeVars : ${[...env.typeVars].map(getHumanReadableName)}
|
||||||
|
env.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])}
|
||||||
|
fn.kind : ${fn.kind}
|
||||||
|
fn.t : ${prettyT(fnResolved.t)}
|
||||||
|
env2.typeVars : ${[...env2.typeVars].map(getHumanReadableName)}
|
||||||
|
env2.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env2.nextFreeTypeVar])}
|
||||||
|
input.kind : ${input.kind}
|
||||||
|
input.t : ${prettyT(inputResolved.t)}
|
||||||
|
env3.typeVars : ${[...env3.typeVars].map(getHumanReadableName)}
|
||||||
|
env3.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env3.nextFreeTypeVar])}
|
||||||
|
=======================`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||||
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!`);
|
|
||||||
}
|
|
||||||
if (getSymbol(fnResolved.t) !== symbolFunction) {
|
if (getSymbol(fnResolved.t) !== symbolFunction) {
|
||||||
// not a function...
|
// not a function...
|
||||||
if (isTypeVar(fnResolved.t)) {
|
if (isTypeVar(fnResolved.t)) {
|
||||||
|
|
@ -157,7 +163,8 @@ export function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, en
|
||||||
// hacky
|
// hacky
|
||||||
const typeVars = env.typeVars.union(occurring(newType)) as Set<string>;
|
const typeVars = env.typeVars.union(occurring(newType)) as Set<string>;
|
||||||
const newEnv: Environment = {
|
const newEnv: Environment = {
|
||||||
names: trie.insert(env.names)(name)(newResolved),
|
// names: trie.insert(env.names)(name)(newResolved),
|
||||||
|
names: env.names,
|
||||||
typeVars,
|
typeVars,
|
||||||
nextFreeTypeVar: highestTypeVar2(typeVars) + 1,
|
nextFreeTypeVar: highestTypeVar2(typeVars) + 1,
|
||||||
};
|
};
|
||||||
|
|
@ -166,24 +173,35 @@ export function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, en
|
||||||
}
|
}
|
||||||
|
|
||||||
function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||||
|
let __debug = '';
|
||||||
try {
|
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!`);
|
||||||
|
}
|
||||||
|
|
||||||
// turn input in to a function
|
// turn input in to a function
|
||||||
const [abstractOutputType, env2] = makeTypeVar(env, "<out>");
|
const [abstractOutputType, env2] = makeTypeVar(env, "<out>");
|
||||||
const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType);
|
const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType);
|
||||||
|
|
||||||
if (VERBOSE) {
|
if (VERBOSE) {
|
||||||
console.log('========= evalCallBlock3 =========')
|
__debug += `========= evalCallBlock3 =========
|
||||||
console.log('env :', env);
|
env.typeVars : ${[...env.typeVars].map(getHumanReadableName)}
|
||||||
console.log('fnKind :', fnResolved.kind);
|
nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])}
|
||||||
console.log('inputKind :', inputResolved.kind);
|
fnKind : ${fnResolved.kind}
|
||||||
console.log('fnType :', prettyT(fnResolved.t));
|
inputKind : ${inputResolved.kind}
|
||||||
console.log('inputType :', prettyT(inputResolved.t));
|
fnType : ${prettyT(fnResolved.t)}
|
||||||
console.log('matchFnType :', prettyT(matchFnType));
|
inputType : ${prettyT(inputResolved.t)}
|
||||||
|
matchFnType : ${prettyT(matchFnType)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// unify both functions
|
// unify both functions
|
||||||
const unification = /*transitivelyGrow*/(unifyLL(fnResolved.t, matchFnType));
|
const unification = /*transitivelyGrow*/(unifyLL(fnResolved.t, matchFnType));
|
||||||
|
|
||||||
|
if (VERBOSE) {
|
||||||
|
__debug += `
|
||||||
|
unification : ${prettyU(unification)}`;
|
||||||
|
}
|
||||||
|
|
||||||
const unificationR = reduceUnification(unification);
|
const unificationR = reduceUnification(unification);
|
||||||
const unifiedFnType = substitute(
|
const unifiedFnType = substitute(
|
||||||
// matchFnType,
|
// matchFnType,
|
||||||
|
|
@ -198,25 +216,24 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
const unificationWithoutOutType = new Map([...unification].filter(([symbol]) => symbol !== abstractOutputType.symbol));
|
const unificationWithoutOutType = new Map([...unification].filter(([symbol]) => symbol !== abstractOutputType.symbol));
|
||||||
|
|
||||||
if (VERBOSE) {
|
if (VERBOSE) {
|
||||||
console.log('unification :', prettyU(unification));
|
__debug += `
|
||||||
console.log('unificationInvR :', prettyRU(unificationR));
|
unificationInvR : ${prettyRU(unificationR)}
|
||||||
console.log('unifiedFnType :', prettyT(unifiedFnType));
|
unifiedFnType : ${prettyT(unifiedFnType)}
|
||||||
console.log('outType :', prettyT(outType));
|
outType : ${prettyT(outType)}
|
||||||
console.log('fn.unification :', prettyU(fnResolved.unification));
|
fn.unification : ${prettyU(fnResolved.unification)}
|
||||||
console.log('input.unification :', prettyU(inputResolved.unification));
|
input.unification : ${prettyU(inputResolved.unification)}
|
||||||
console.log('unificationWithoutOutType:', prettyU(unificationWithoutOutType));
|
unificationWithoutOutType: ${prettyU(unificationWithoutOutType)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const grandUnification = [fnResolved.unification, inputResolved.unification]
|
const grandUnification = transitivelyGrow([fnResolved.unification, inputResolved.unification]
|
||||||
.reduce(mergeUnifications, unificationWithoutOutType);
|
.reduce(mergeUnifications, unificationWithoutOutType));
|
||||||
|
|
||||||
// const grandUnification = unificationWithoutOutType;
|
|
||||||
|
|
||||||
if (VERBOSE) {
|
if (VERBOSE) {
|
||||||
console.log('grandUnification :', prettyU(grandUnification));
|
__debug += `
|
||||||
console.log('==================================')
|
grandUnification : ${prettyU(grandUnification)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (inputResolved.kind === "error") {
|
if (inputResolved.kind === "error") {
|
||||||
// throw inputResolved.e;
|
// throw inputResolved.e;
|
||||||
return [{
|
return [{
|
||||||
|
|
@ -225,6 +242,8 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
depth: 0,
|
depth: 0,
|
||||||
t: outType,
|
t: outType,
|
||||||
unification: grandUnification,
|
unification: grandUnification,
|
||||||
|
// @ts-ignore
|
||||||
|
__debug,
|
||||||
}, newEnv];
|
}, newEnv];
|
||||||
}
|
}
|
||||||
if (fnResolved.kind === "error") {
|
if (fnResolved.kind === "error") {
|
||||||
|
|
@ -236,6 +255,8 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
depth: fnResolved.depth+1,
|
depth: fnResolved.depth+1,
|
||||||
t: outType,
|
t: outType,
|
||||||
unification: grandUnification,
|
unification: grandUnification,
|
||||||
|
// @ts-ignore
|
||||||
|
__debug,
|
||||||
}, newEnv];
|
}, newEnv];
|
||||||
}
|
}
|
||||||
// if the above statement did not throw => types are compatible...
|
// if the above statement did not throw => types are compatible...
|
||||||
|
|
@ -247,6 +268,8 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
i: outValue,
|
i: outValue,
|
||||||
t: outType,
|
t: outType,
|
||||||
unification: grandUnification,
|
unification: grandUnification,
|
||||||
|
// @ts-ignore
|
||||||
|
__debug,
|
||||||
}, newEnv];
|
}, newEnv];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -255,32 +278,23 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
kind: "unknown",
|
kind: "unknown",
|
||||||
t: outType,
|
t: outType,
|
||||||
unification: grandUnification,
|
unification: grandUnification,
|
||||||
|
// @ts-ignore
|
||||||
|
__debug,
|
||||||
}, newEnv];
|
}, newEnv];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
// if ((e instanceof UnifyError)) {
|
// if ((e instanceof UnifyError)) {
|
||||||
if (VERBOSE) {
|
if (VERBOSE) {
|
||||||
console.log('UnifyError!', (e as Error).message);
|
__debug += '\n\nUnifyError! ' + (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 [{
|
|
||||||
kind: "error",
|
|
||||||
e: e as Error,
|
|
||||||
depth: 0,
|
|
||||||
t: outType,
|
|
||||||
unification: mergeUnifications(fnResolved.unification, inputResolved.unification), // may throw!
|
|
||||||
}, env];
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
if ((e instanceof UnifyError)) {
|
|
||||||
return makeError(env, e);
|
|
||||||
}
|
}
|
||||||
|
const err = makeError(env, e as Error);
|
||||||
|
// @ts-ignore
|
||||||
|
err[0].__debug = __debug;
|
||||||
|
return err;
|
||||||
|
// }
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||||
|
|
@ -293,18 +307,13 @@ const prettyRU = (rUni: Map<string, Type>) => {
|
||||||
return '{'+[...rUni].map(([symbol,type]) => `${getHumanReadableName(symbol)} => ${prettyT(type)}`).join(', ')+'}';
|
return '{'+[...rUni].map(([symbol,type]) => `${getHumanReadableName(symbol)} => ${prettyT(type)}`).join(', ')+'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment,Environment] {
|
||||||
const [paramType, staticInnerEnv] = makeTypeVar(env, paramName);
|
let [paramType, innerEnv] = makeTypeVar(env, paramName);
|
||||||
|
let round = 0;
|
||||||
if (VERBOSE) {
|
let __debug = '';
|
||||||
console.log('====== begin evalLambdaBlock ======')
|
while (true) {
|
||||||
console.log('paramName :', paramName);
|
// fixpoint computation...
|
||||||
console.log('paramType :', prettyT(paramType));
|
const [exprResolved] = evalExprBlock(expr, innerEnv);
|
||||||
console.log('staticInnerEnv:', staticInnerEnv);
|
|
||||||
console.log('===================================')
|
|
||||||
}
|
|
||||||
|
|
||||||
const [exprResolved] = evalExprBlock(expr, staticInnerEnv);
|
|
||||||
const lambdaT = fnType(_ => paramType)(_ => exprResolved.t);
|
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:
|
// 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 reduced = reduceUnification(exprResolved.unification);
|
||||||
|
|
@ -312,6 +321,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En
|
||||||
lambdaT,
|
lambdaT,
|
||||||
reduced,
|
reduced,
|
||||||
[]); // <- not important
|
[]); // <- not important
|
||||||
|
|
||||||
let lambdaResolved: ResolvedType;
|
let lambdaResolved: ResolvedType;
|
||||||
if (exprResolved.kind === "error") {
|
if (exprResolved.kind === "error") {
|
||||||
lambdaResolved = {
|
lambdaResolved = {
|
||||||
|
|
@ -323,7 +333,6 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted);
|
|
||||||
lambdaResolved = {
|
lambdaResolved = {
|
||||||
kind: "value",
|
kind: "value",
|
||||||
t: lambdaTSubstituted,
|
t: lambdaTSubstituted,
|
||||||
|
|
@ -331,7 +340,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En
|
||||||
const innerEnv = makeInnerEnv(env, paramName, {
|
const innerEnv = makeInnerEnv(env, paramName, {
|
||||||
kind: "value",
|
kind: "value",
|
||||||
i: x,
|
i: x,
|
||||||
t: paramTypeSubstituted,
|
t: lambdaTSubstituted,
|
||||||
unification: new Map(),
|
unification: new Map(),
|
||||||
});
|
});
|
||||||
const [result] = evalExprBlock(expr, innerEnv);
|
const [result] = evalExprBlock(expr, innerEnv);
|
||||||
|
|
@ -343,24 +352,50 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const [lambdaResolvedNormalized, resultEnv] = recomputeTypeVarsForEnv(paramName, lambdaResolved, env);
|
const [betterResolved, newInnerEnv] = recomputeTypeVarsForEnv("<name unused>", lambdaResolved, env);
|
||||||
|
|
||||||
|
const inferredParamType = betterResolved.t.params[0](betterResolved.t);
|
||||||
|
|
||||||
if (VERBOSE) {
|
if (VERBOSE) {
|
||||||
console.log('======= end evalLambdaBlock =======')
|
__debug += `
|
||||||
console.log('paramType :', prettyT(paramType));
|
====== evalLambdaBlock (round ${round}) ======
|
||||||
console.log('exprType :', prettyT(exprResolved.t));
|
env.typeVars : ${[...env.typeVars].map(getHumanReadableName)}
|
||||||
console.log('exprUnification :', prettyU(exprResolved.unification));
|
nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])}
|
||||||
console.log('exprUnificationR :', prettyRU(reduced));
|
paramName : ${paramName}
|
||||||
console.log('lambdaType :', prettyT(lambdaT));
|
paramType : ${prettyT(paramType)}
|
||||||
console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted));
|
staticInnerEnv: ${innerEnv}
|
||||||
console.log('===================================')
|
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)}`;
|
||||||
}
|
}
|
||||||
return [lambdaResolved, env];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function haveValue(resolved: ResolvedType) {
|
if (eqType(paramType)(inferredParamType)) {
|
||||||
// return resolved && !(resolved instanceof DeepError);
|
// reached fixpoint
|
||||||
return resolved.kind === "value";
|
// @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] {
|
function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] {
|
||||||
|
|
@ -414,22 +449,16 @@ export function attemptParseLiteral(text: string, env: Environment): Dynamic[] {
|
||||||
export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) {
|
export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) {
|
||||||
const bias = outPriority(resolved);
|
const bias = outPriority(resolved);
|
||||||
|
|
||||||
console.log('scoreResolved...', resolved);
|
|
||||||
|
|
||||||
if (resolved.kind === "value") {
|
if (resolved.kind === "value") {
|
||||||
console.log('best:', 2, bias);
|
return bias;
|
||||||
return 2 + bias;
|
|
||||||
}
|
}
|
||||||
else if (resolved.kind === "unknown") {
|
else if (resolved.kind === "unknown") {
|
||||||
console.log('ok:', 1, bias);
|
return bias;
|
||||||
return 1 + bias;
|
|
||||||
}
|
}
|
||||||
if (resolved.e instanceof UnifyError) {
|
if (resolved.e instanceof UnifyError) {
|
||||||
console.log('bad:', -1, bias);
|
|
||||||
return -1 + bias; // parameter doesn't match
|
return -1 + bias; // parameter doesn't match
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log('worst:', -2, bias);
|
|
||||||
return -2 + bias; // even worse: fn is not a function!
|
return -2 + bias; // even worse: fn is not a function!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue