From 2333abe70f23e1ead428241fbe7c21700072d05b Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 23 May 2025 14:35:07 +0200 Subject: [PATCH 1/2] cleanup unification a bit --- lib/generics/unify.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/generics/unify.js b/lib/generics/unify.js index 1896371..e727565 100644 --- a/lib/generics/unify.js +++ b/lib/generics/unify.js @@ -29,6 +29,7 @@ export class SubstitutionCycle extends Error { } } +// pure export const subsitutionsEqual = (m1,m2) => { if (m1.size !== m2.size ) return false; for (const [key1,type1] of m1) { @@ -37,6 +38,7 @@ export const subsitutionsEqual = (m1,m2) => { return true; }; +// pure // Partial ordering between types // - deep-equal types are equal (e.g., Int == Int) // - non-typevars are smaller than typevars (e.g., Int < a) @@ -68,17 +70,20 @@ const partialCompareTypes = (typeA, typeB) => { } } +// pure const checkCycle = (typevar, type) => { if (occurring(type).has(typevar)) { throw new SubstitutionCycle(typevar, type); } } +// impure, modifies 'substitutions' const addReduce = (substitutions, typevar, type) => { // console.log('add ', prettyS(typevar, type)); substitutions.set(typevar, type); } +// impure, modifies 'substitutions' const attemptReduce = (substitutions, typevar, type) => { // assuming 'substitutions' is already reduced as much as possible, // substitute all typevars in our type with the existing substitutions @@ -108,8 +113,24 @@ 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) => { - const substitutions = new Map(); try { if (isTypeVar(typeA)) { if (isTypeVar(typeB)) { @@ -139,20 +160,14 @@ export const unify = (typeA, typeB) => { throw new IncompabibleTypesError(typeA, typeB); } - const unifiedParams = zip(typeA.params, typeB.params) + const subs = zip(typeA.params, typeB.params) .map(([getParamA, getParamB]) => { const paramA = getParamA(typeA); const paramB = getParamB(typeB); // console.log('request...'); return unify(paramA, paramB); }); - - // merge substitutions - unifiedParams.forEach(subst => { - for (const [typevar, type] of subst) { - attemptReduce(substitutions, typevar, type); - } - }); + return mergeSubstitutionsN(subs); } catch (e) { if (e instanceof SubstitutionCycle) { @@ -164,5 +179,4 @@ export const unify = (typeA, typeB) => { } throw e; } - return substitutions; }; From 3d72f4d416a4ce827f4f0cd0a9515185e0105d74 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 23 May 2025 14:45:35 +0200 Subject: [PATCH 2/2] get rid of some stuff --- index.d.ts | 25 ++++++++------------ index.js | 2 +- lib/environment/env.js | 33 +++++++++++++------------- lib/generics/generics.js | 41 +-------------------------------- lib/primitives/dynamic.js | 20 ++++++++-------- lib/primitives/dynamic.types.js | 3 +-- 6 files changed, 38 insertions(+), 86 deletions(-) diff --git a/index.d.ts b/index.d.ts index 4767b58..d0b4f65 100644 --- a/index.d.ts +++ b/index.d.ts @@ -165,16 +165,13 @@ export const unit: { export function RBTreeWrapper(...args: any[]): any; -export class UnifyError extends Error {}; - -export class NotAFunctionError extends Error {}; +export class IncompatibleTypesError extends Error {}; +export class SubstitutionCycle extends Error {}; export function addDouble(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 assignFnSubstitutions(funType: any, paramType: any, skip?: number): [any, any, any, any]; @@ -243,8 +240,6 @@ export function getCompatibleInputTypes(env: any): any; export function getDefaultTypeParser(): any; -export function getEnabledFunctions(env: any): any; - export function getFunctions(env: any): any; export function getHumanReadableName(symbol: any): any; @@ -334,9 +329,9 @@ export function pretty(obj: any): any; export function prettyT(type: any): any; -export function prettyU(unification: any): string; +export function prettyS(typevar: any, type: any): string; -export function prettyST(setOfTypes: any): string; +export function prettySS(substitution: Map): string; export function prodType(typeParam: any): any; @@ -350,17 +345,15 @@ export function structType(fields: any, rootSelf: 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 transitivelyGrow(uni: any): 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 unify(typeA, typeB): any; export function zip(a: any, b: any): any; diff --git a/index.js b/index.js index ffa0911..3b3934c 100644 --- a/index.js +++ b/index.js @@ -16,7 +16,7 @@ export * from "./lib/structures/enum.types.js"; export * from "./lib/structures/product.js"; export * from "./lib/parser/type_parser.js"; export * from "./lib/generics/generics.js"; -export * from "./lib/generics/low_level.js"; +export * from "./lib/generics/unify.js"; export * from "./lib/stdlib.js"; export * from "./lib/compare/type.js"; export * from "./lib/compare/dynamic.js"; diff --git a/lib/environment/env.js b/lib/environment/env.js index d8cc6f2..9f03f05 100644 --- a/lib/environment/env.js +++ b/lib/environment/env.js @@ -1,6 +1,5 @@ import { makeCompareFn } from "../compare/dynamic.js"; import { compareTypes } from "../compare/type.js"; -import { assignFn, UnifyError } from "../generics/generics.js"; import { getInst, getType, newDynamic } from "../primitives/dynamic.js"; import { Dynamic, Type } from "../primitives/primitive_types.js"; import { getSymbol } from "../primitives/type.js"; @@ -77,22 +76,22 @@ export const getFunctions = env => { })([])(types); }; -// return list of functions that can be called on 'dynamic' -export const getEnabledFunctions = env => dynamic => { - const allFunctions = getFunctions(env); - const enabled = foldList(enabled => fun => { - try { - const outType = assignFn(getType(fun), getType(dynamic)); - return [...enabled, {fun, outType}]; - } catch (e) { - if (!(e instanceof UnifyError)) { - throw e; - } - return enabled; - } - })([])(allFunctions); - return enabled; -}; +// // return list of functions that can be called on 'dynamic' +// export const getEnabledFunctions = env => dynamic => { +// const allFunctions = getFunctions(env); +// const enabled = foldList(enabled => fun => { +// try { +// const outType = assignFn(getType(fun), getType(dynamic)); +// return [...enabled, {fun, outType}]; +// } catch (e) { +// if (!(e instanceof UnifyError)) { +// throw e; +// } +// return enabled; +// } +// })([])(allFunctions); +// return enabled; +// }; export const getCompatibleInputTypes = env => funType => { const allTypes = getTypes(env); diff --git a/lib/generics/generics.js b/lib/generics/generics.js index 71ef01a..e6a4335 100644 --- a/lib/generics/generics.js +++ b/lib/generics/generics.js @@ -1,10 +1,7 @@ import { inspect } from "node:util"; -import { inspectType, makeTypeConstructor } from "../meta/type_constructor.js"; +import { inspectType } from "../meta/type_constructor.js"; import { getSymbol } from "../primitives/type.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 // for instance, the type: @@ -35,18 +32,6 @@ const _occurring = stack => type => { // Get set of type variables in type. 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=[]) => { // console.log('substitute...', {type, substitutions, stack}); const found = substitutions.get(getSymbol(type)); @@ -67,30 +52,6 @@ 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 export const recomputeTypeVars = (types, skip=0) => { return recomputeTypeVarsWithInverse(types, skip)[0]; diff --git a/lib/primitives/dynamic.js b/lib/primitives/dynamic.js index 7f6582e..5fae380 100644 --- a/lib/primitives/dynamic.js +++ b/lib/primitives/dynamic.js @@ -1,5 +1,5 @@ import { inspect } from "node:util"; -import { assignFn } from "../generics/generics.js"; +// import { assignFn } from "../generics/generics.js"; function inspectDynamic(_depth, options, inspect) { 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 getType = lnk => lnk.t; -export const apply = input => fun => { - const inputType = getType(input) - const funType = getType(fun); - const outputType = assignFn(funType, inputType); +// export const apply = input => fun => { +// const inputType = getType(input) +// const funType = getType(fun); +// const outputType = assignFn(funType, inputType); - const inputValue = getInst(input); - const funValue = getInst(fun); - const outputValue = funValue(inputValue); +// const inputValue = getInst(input); +// const funValue = getInst(fun); +// const outputValue = funValue(inputValue); - return newDynamic(outputValue)(outputType); -}; +// return newDynamic(outputValue)(outputType); +// }; diff --git a/lib/primitives/dynamic.types.js b/lib/primitives/dynamic.types.js index ca472d1..4309fde 100644 --- a/lib/primitives/dynamic.types.js +++ b/lib/primitives/dynamic.types.js @@ -1,5 +1,5 @@ import { getDefaultTypeParser } from "../parser/type_parser.js"; -import { apply, getInst, getType, newDynamic } from "./dynamic.js"; +import { getInst, getType, newDynamic } from "./dynamic.js"; const mkType = getDefaultTypeParser(); @@ -9,5 +9,4 @@ export const ModuleDynamic = [ ["newDynamic", newDynamic(newDynamic)(mkType("a -> Type -> Dynamic" ))], ["getInst" , newDynamic(getInst )(mkType("Dynamic -> a" ))], ["getType" , newDynamic(getType )(mkType("Dynamic -> Type" ))], - ["apply" , newDynamic(apply )(mkType("Dynamic -> Dynamic -> Dynamic"))], ];