Compare commits

..

No commits in common. "3d72f4d416a4ce827f4f0cd0a9515185e0105d74" and "68bd7cdb9fe91bf1fd9fb4544d45c37c6f8592ae" have entirely different histories.

7 changed files with 96 additions and 62 deletions

25
index.d.ts vendored
View file

@ -165,13 +165,16 @@ export const unit: {
export function RBTreeWrapper(...args: any[]): any; export function RBTreeWrapper(...args: any[]): any;
export class IncompatibleTypesError extends Error {}; export class UnifyError extends Error {};
export class SubstitutionCycle extends Error {};
export class NotAFunctionError extends Error {};
export function addDouble(x: any): any; export function addDouble(x: any): any;
export function addInt(x: any): any; export function addInt(x: any): any;
export function apply(input: any): any;
export function assignFn(funType: any, paramType: any): any; export function assignFn(funType: any, paramType: any): any;
export function assignFnSubstitutions(funType: any, paramType: any, skip?: number): [any, any, any, any]; export function assignFnSubstitutions(funType: any, paramType: any, skip?: number): [any, any, any, any];
@ -240,6 +243,8 @@ export function getCompatibleInputTypes(env: any): any;
export function getDefaultTypeParser(): any; export function getDefaultTypeParser(): any;
export function getEnabledFunctions(env: any): any;
export function getFunctions(env: any): any; export function getFunctions(env: any): any;
export function getHumanReadableName(symbol: any): any; export function getHumanReadableName(symbol: any): any;
@ -329,9 +334,9 @@ export function pretty(obj: any): any;
export function prettyT(type: any): any; export function prettyT(type: any): any;
export function prettyS(typevar: any, type: any): string; export function prettyU(unification: any): string;
export function prettySS(substitution: Map<string, any>): string; export function prettyST(setOfTypes: any): string;
export function prodType(typeParam: any): any; export function prodType(typeParam: any): any;
@ -345,15 +350,17 @@ export function structType(fields: any, rootSelf: any): any;
export function substitute(type: any, substitutions: any, stack: any): any; export function substitute(type: any, substitutions: any, stack: any): any;
export function subsitutionsEqual(m1: any, m2: any): boolean;
export function mergeSubstitutionsN(subs: any): any;
export function sumType(typeParam: any): any; export function sumType(typeParam: any): any;
export function transitivelyGrow(uni: any): any; export function transitivelyGrow(uni: any): any;
export function unify(typeA, typeB): any; export function unify(fType: any, aType: any): any;
export function unifyLL(typeA: any, typeB: any): any;
export function mergeUnifications(uniA: any, uniB: any): any;
export function reduceUnification(uni: any): any;
export function zip(a: any, b: any): any; export function zip(a: any, b: any): any;

View file

@ -16,7 +16,7 @@ export * from "./lib/structures/enum.types.js";
export * from "./lib/structures/product.js"; export * from "./lib/structures/product.js";
export * from "./lib/parser/type_parser.js"; export * from "./lib/parser/type_parser.js";
export * from "./lib/generics/generics.js"; export * from "./lib/generics/generics.js";
export * from "./lib/generics/unify.js"; export * from "./lib/generics/low_level.js";
export * from "./lib/stdlib.js"; export * from "./lib/stdlib.js";
export * from "./lib/compare/type.js"; export * from "./lib/compare/type.js";
export * from "./lib/compare/dynamic.js"; export * from "./lib/compare/dynamic.js";

View file

@ -1,5 +1,6 @@
import { makeCompareFn } from "../compare/dynamic.js"; import { makeCompareFn } from "../compare/dynamic.js";
import { compareTypes } from "../compare/type.js"; import { compareTypes } from "../compare/type.js";
import { assignFn, UnifyError } from "../generics/generics.js";
import { getInst, getType, newDynamic } from "../primitives/dynamic.js"; import { getInst, getType, newDynamic } from "../primitives/dynamic.js";
import { Dynamic, Type } from "../primitives/primitive_types.js"; import { Dynamic, Type } from "../primitives/primitive_types.js";
import { getSymbol } from "../primitives/type.js"; import { getSymbol } from "../primitives/type.js";
@ -76,22 +77,22 @@ export const getFunctions = env => {
})([])(types); })([])(types);
}; };
// // return list of functions that can be called on 'dynamic' // return list of functions that can be called on 'dynamic'
// export const getEnabledFunctions = env => dynamic => { export const getEnabledFunctions = env => dynamic => {
// const allFunctions = getFunctions(env); const allFunctions = getFunctions(env);
// const enabled = foldList(enabled => fun => { const enabled = foldList(enabled => fun => {
// try { try {
// const outType = assignFn(getType(fun), getType(dynamic)); const outType = assignFn(getType(fun), getType(dynamic));
// return [...enabled, {fun, outType}]; return [...enabled, {fun, outType}];
// } catch (e) { } catch (e) {
// if (!(e instanceof UnifyError)) { if (!(e instanceof UnifyError)) {
// throw e; throw e;
// } }
// return enabled; return enabled;
// } }
// })([])(allFunctions); })([])(allFunctions);
// return enabled; return enabled;
// }; };
export const getCompatibleInputTypes = env => funType => { export const getCompatibleInputTypes = env => funType => {
const allTypes = getTypes(env); const allTypes = getTypes(env);

View file

@ -1,7 +1,10 @@
import { inspect } from "node:util"; import { inspect } from "node:util";
import { inspectType } from "../meta/type_constructor.js"; import { inspectType, makeTypeConstructor } from "../meta/type_constructor.js";
import { getSymbol } from "../primitives/type.js"; import { getSymbol } from "../primitives/type.js";
import { isTypeVar, TYPE_VARS, UNBOUND_SYMBOLS } from "../primitives/typevars.js"; import { isTypeVar, TYPE_VARS, UNBOUND_SYMBOLS } from "../primitives/typevars.js";
import { symbolFunction } from "../structures/type_constructors.js";
import { prettyT } from '../util/pretty.js';
import { reduceUnification, unifyLL } from "./low_level.js";
// helper for creating generic types // helper for creating generic types
// for instance, the type: // for instance, the type:
@ -32,6 +35,18 @@ const _occurring = stack => type => {
// Get set of type variables in type. // Get set of type variables in type.
export const occurring = _occurring([]); export const occurring = _occurring([]);
export class UnifyError extends Error {}
export class NotAFunctionError extends Error {}
export const unify = (fType, aType) => {
[fType, aType] = recomputeTypeVars([fType, aType]);
const unification = unifyLL(fType, aType);
const substitutions = reduceUnification(unification);
const uType = substitute(fType, // or aType, doesn't matter here
substitutions);
return recomputeTypeVars([uType])[0];
};
export const substitute = (type, substitutions, stack=[]) => { export const substitute = (type, substitutions, stack=[]) => {
// console.log('substitute...', {type, substitutions, stack}); // console.log('substitute...', {type, substitutions, stack});
const found = substitutions.get(getSymbol(type)); const found = substitutions.get(getSymbol(type));
@ -52,6 +67,30 @@ export const substitute = (type, substitutions, stack=[]) => {
}; };
}; };
export const assignFn = (funType, paramType, skip=0) => {
// Precondition
if (getSymbol(funType) !== symbolFunction) {
throw new NotAFunctionError(`${prettyT(funType)} is not a function type!`);
}
// Step 1: Very important: Function and parameter type may have overlapping type variables, so we recompute them to make them non-overlapping:
const [funType1, paramType1] = recomputeTypeVars([funType, paramType]);
// Step 2: Get input and output type of function
const [inType1, outType1] = funType1.params.map(p => p(funType1));
// Step 3: Unify parameter type with input type
const unifInType1 = unifyLL(inType1, paramType1);
// Step 4: Substitute typevars in output type
const substInType1 = reduceUnification(unifInType1);
const reducedOutType1 = substitute(outType1, substInType1);
// Step 5: 'Normalize' output type
const [outType] = recomputeTypeVars([reducedOutType1], skip);
return outType;
}
// Ensures that no type variables overlap // Ensures that no type variables overlap
export const recomputeTypeVars = (types, skip=0) => { export const recomputeTypeVars = (types, skip=0) => {
return recomputeTypeVarsWithInverse(types, skip)[0]; return recomputeTypeVarsWithInverse(types, skip)[0];

View file

@ -29,7 +29,6 @@ export class SubstitutionCycle extends Error {
} }
} }
// pure
export const subsitutionsEqual = (m1,m2) => { export const subsitutionsEqual = (m1,m2) => {
if (m1.size !== m2.size ) return false; if (m1.size !== m2.size ) return false;
for (const [key1,type1] of m1) { for (const [key1,type1] of m1) {
@ -38,7 +37,6 @@ export const subsitutionsEqual = (m1,m2) => {
return true; return true;
}; };
// pure
// Partial ordering between types // Partial ordering between types
// - deep-equal types are equal (e.g., Int == Int) // - deep-equal types are equal (e.g., Int == Int)
// - non-typevars are smaller than typevars (e.g., Int < a) // - non-typevars are smaller than typevars (e.g., Int < a)
@ -70,20 +68,17 @@ const partialCompareTypes = (typeA, typeB) => {
} }
} }
// pure
const checkCycle = (typevar, type) => { const checkCycle = (typevar, type) => {
if (occurring(type).has(typevar)) { if (occurring(type).has(typevar)) {
throw new SubstitutionCycle(typevar, type); throw new SubstitutionCycle(typevar, type);
} }
} }
// impure, modifies 'substitutions'
const addReduce = (substitutions, typevar, type) => { const addReduce = (substitutions, typevar, type) => {
// console.log('add ', prettyS(typevar, type)); // console.log('add ', prettyS(typevar, type));
substitutions.set(typevar, type); substitutions.set(typevar, type);
} }
// impure, modifies 'substitutions'
const attemptReduce = (substitutions, typevar, type) => { const attemptReduce = (substitutions, typevar, type) => {
// assuming 'substitutions' is already reduced as much as possible, // assuming 'substitutions' is already reduced as much as possible,
// substitute all typevars in our type with the existing substitutions // substitute all typevars in our type with the existing substitutions
@ -113,24 +108,8 @@ const attemptReduce = (substitutions, typevar, type) => {
} }
} }
// pure
export const mergeSubstitutionsN = (substs) => {
return substs.reduce((acc, cur) => {
return mergeSubstitutions2(acc, cur);
}, new Map());
}
// pure
const mergeSubstitutions2 = (substA, substB) => {
const result = new Map(substA);
for (const [typevarB, typeB] of substB) {
attemptReduce(result, typevarB, typeB);
}
return result;
}
// pure
export const unify = (typeA, typeB) => { export const unify = (typeA, typeB) => {
const substitutions = new Map();
try { try {
if (isTypeVar(typeA)) { if (isTypeVar(typeA)) {
if (isTypeVar(typeB)) { if (isTypeVar(typeB)) {
@ -160,14 +139,20 @@ export const unify = (typeA, typeB) => {
throw new IncompabibleTypesError(typeA, typeB); throw new IncompabibleTypesError(typeA, typeB);
} }
const subs = zip(typeA.params, typeB.params) const unifiedParams = zip(typeA.params, typeB.params)
.map(([getParamA, getParamB]) => { .map(([getParamA, getParamB]) => {
const paramA = getParamA(typeA); const paramA = getParamA(typeA);
const paramB = getParamB(typeB); const paramB = getParamB(typeB);
// console.log('request...'); // console.log('request...');
return unify(paramA, paramB); return unify(paramA, paramB);
}); });
return mergeSubstitutionsN(subs);
// merge substitutions
unifiedParams.forEach(subst => {
for (const [typevar, type] of subst) {
attemptReduce(substitutions, typevar, type);
}
});
} }
catch (e) { catch (e) {
if (e instanceof SubstitutionCycle) { if (e instanceof SubstitutionCycle) {
@ -179,4 +164,5 @@ export const unify = (typeA, typeB) => {
} }
throw e; throw e;
} }
return substitutions;
}; };

View file

@ -1,5 +1,5 @@
import { inspect } from "node:util"; import { inspect } from "node:util";
// import { assignFn } from "../generics/generics.js"; import { assignFn } from "../generics/generics.js";
function inspectDynamic(_depth, options, inspect) { function inspectDynamic(_depth, options, inspect) {
return `${inspect(this.i, options)} :: ${inspect(this.t, options)}`; return `${inspect(this.i, options)} :: ${inspect(this.t, options)}`;
@ -13,14 +13,14 @@ export const newDynamic = i => t => ({
export const getInst = lnk => lnk.i; export const getInst = lnk => lnk.i;
export const getType = lnk => lnk.t; export const getType = lnk => lnk.t;
// export const apply = input => fun => { export const apply = input => fun => {
// const inputType = getType(input) const inputType = getType(input)
// const funType = getType(fun); const funType = getType(fun);
// const outputType = assignFn(funType, inputType); const outputType = assignFn(funType, inputType);
// const inputValue = getInst(input); const inputValue = getInst(input);
// const funValue = getInst(fun); const funValue = getInst(fun);
// const outputValue = funValue(inputValue); const outputValue = funValue(inputValue);
// return newDynamic(outputValue)(outputType); return newDynamic(outputValue)(outputType);
// }; };

View file

@ -1,5 +1,5 @@
import { getDefaultTypeParser } from "../parser/type_parser.js"; import { getDefaultTypeParser } from "../parser/type_parser.js";
import { getInst, getType, newDynamic } from "./dynamic.js"; import { apply, getInst, getType, newDynamic } from "./dynamic.js";
const mkType = getDefaultTypeParser(); const mkType = getDefaultTypeParser();
@ -9,4 +9,5 @@ export const ModuleDynamic = [
["newDynamic", newDynamic(newDynamic)(mkType("a -> Type -> Dynamic" ))], ["newDynamic", newDynamic(newDynamic)(mkType("a -> Type -> Dynamic" ))],
["getInst" , newDynamic(getInst )(mkType("Dynamic -> a" ))], ["getInst" , newDynamic(getInst )(mkType("Dynamic -> a" ))],
["getType" , newDynamic(getType )(mkType("Dynamic -> Type" ))], ["getType" , newDynamic(getType )(mkType("Dynamic -> Type" ))],
["apply" , newDynamic(apply )(mkType("Dynamic -> Dynamic -> Dynamic"))],
]; ];