From 35d682429be8975f32df3ec374880d0d25e13960 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Thu, 8 May 2025 17:30:42 +0200 Subject: [PATCH] turn one example into a test + fix bug in type variable substition function --- examples/parser.js | 0 examples/recursive_types.js | 97 ------------------------- lib/generics/generics.js | 17 +++-- tests/recursive_types.js | 136 ++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 102 deletions(-) delete mode 100644 examples/parser.js delete mode 100644 examples/recursive_types.js create mode 100644 tests/recursive_types.js diff --git a/examples/parser.js b/examples/parser.js deleted file mode 100644 index e69de29..0000000 diff --git a/examples/recursive_types.js b/examples/recursive_types.js deleted file mode 100644 index 6a8c7d2..0000000 --- a/examples/recursive_types.js +++ /dev/null @@ -1,97 +0,0 @@ -import { compareTypes } from "../lib/compare/type.js"; -import { makeGeneric, substitute, unify } from "../lib/generics/generics.js"; -import { Double, Int, Unit } from "../lib/primitives/primitive_types.js"; -import { TYPE_VARS } from "../lib/primitives/typevars.js"; -import { fnType, lsType, prodType, sumType, setType } from "../lib/structures/type_constructors.js"; -import { prettyT } from "../lib/util/pretty.js"; - -Error.stackTraceLimit = Infinity; - -// some recursive types: - -const listOfSetOfSelf = lsType(self => setType(_ => self)); - -const makeLinkedList = elementType => sumType - (self => prodType - (_ => elementType) - (_ => self)) - (_ => Unit); - -const linkedListOfInt = makeLinkedList(Int); -const linkedListOfDouble = makeLinkedList(Double); - -// some generic types - -const genericFunction = makeGeneric((a,b) => fnType(_ => a)(_ => b)); -const genericLinkedList = makeGeneric(a => makeLinkedList(a)); - -// pretty-printing of recursive types: - -console.log(prettyT(listOfSetOfSelf)); // #0[{#0}] -console.log(prettyT(linkedListOfInt)); // #0((Int ⨯ #0) + Unit) -console.log(prettyT(genericFunction)); // (a -> b) -console.log(prettyT(genericLinkedList)); // #0((a ⨯ #0) + Unit) - -// comparison - -console.log(compareTypes(listOfSetOfSelf)(listOfSetOfSelf)) // 0 -console.log(compareTypes(linkedListOfInt)(linkedListOfInt)) // 0 -console.log(compareTypes(linkedListOfInt)(linkedListOfDouble)) // 1 -console.log(compareTypes(linkedListOfDouble)(linkedListOfInt)) // -1 -console.log(compareTypes(linkedListOfDouble)(linkedListOfDouble)) // 0 -console.log(compareTypes(linkedListOfDouble)(listOfSetOfSelf)) // 1 -console.log(compareTypes(listOfSetOfSelf)(linkedListOfDouble)) // -1 - - -const genericList = makeGeneric(a => lsType(_ => a)); -const intList = lsType(_ => Int); - -console.log(prettyT(genericList)); // [a] - -// substitution of type parameters - -const substituted = substitute( - genericList, - new Map([ - [TYPE_VARS[0], Int], - ]) -); - -console.log(prettyT(substituted)); // [Int] - -// substitution (recursive this time) - -console.log("recursive substitution") -console.log(prettyT(substitute( - genericLinkedList, - new Map([ - [TYPE_VARS[0], Int], - ]) -))); // #0((Int ⨯ #0) + Unit) - -// unification (simple case) - -const type = unify( - genericList, - makeGeneric(() => intList)); - -console.log(prettyT(type)); // [Int] - -// unification (recursive case) - -console.log("complex case...") - -const unified = unify( - genericLinkedList, - makeGeneric(() => linkedListOfInt)); - -console.log(prettyT(unified)); // #0((Int ⨯ #0) + Unit) - -// unification (strange case) - -const unified2 = unify( - makeGeneric(() => listOfSetOfSelf), - genericList, -); - -console.log(prettyT(unified2)); // #0[{#0}] diff --git a/lib/generics/generics.js b/lib/generics/generics.js index d6a4e5b..37d3fbb 100644 --- a/lib/generics/generics.js +++ b/lib/generics/generics.js @@ -163,17 +163,24 @@ export const unify = (fType, aType) => { export const substitute = (type, substitutions, stack=[]) => { // console.log('substitute...', {type, substitutions, stack}); - return substitutions.get(getSymbol(type)) - || { + const found = substitutions.get(getSymbol(type)); + if (found) { + return found; + } + return { symbol: getSymbol(type), params: type.params.map(getParam => parent => { - const param = getParam(stack.length); - return stack[param] - || substitute(param, substitutions, [...stack, parent]); + const param = getParam(parent); + if (stack.includes(param)) { + // param points back up - that's ok - means we don't have to recurse + return param; + } + return substitute(param, substitutions, [...stack, parent]); }), }; }; + export const assignFn = (funType, paramType) => { [funType, paramType] = recomputeTypeVars([funType, paramType]); // console.log(prettyT(funType), prettyT(paramType)); diff --git a/tests/recursive_types.js b/tests/recursive_types.js new file mode 100644 index 0000000..407839c --- /dev/null +++ b/tests/recursive_types.js @@ -0,0 +1,136 @@ +import assert from "node:assert"; +import { compareTypes } from "../lib/compare/type.js"; +import { makeGeneric, substitute, unify } from "../lib/generics/generics.js"; +import { Double, Int, Unit } from "../lib/primitives/primitive_types.js"; +import { UNBOUND_SYMBOLS } from "../lib/primitives/typevars.js"; +import { fnType, lsType, prodType, sumType, setType } from "../lib/structures/type_constructors.js"; +import { prettyT } from "../lib/util/pretty.js"; + +// some recursive types: + +const listOfSetOfSelf = lsType(self => setType(_ => self)); + +const makeLinkedList = elementType => + sumType + (self => prodType + (_ => elementType) + (_ => self)) + (_ => Unit); + +const linkedListOfInt = makeLinkedList(Int); +const linkedListOfDouble = makeLinkedList(Double); + +// some generic types + +const genericFunction = makeGeneric((a,b) => fnType(_ => a)(_ => b)); +const genericLinkedList = makeGeneric(a => makeLinkedList(a)); + + +// pretty-printing of recursive types: + +assert.equal( + prettyT(listOfSetOfSelf), + "#0[{#0}]", +); + +assert.equal( + prettyT(linkedListOfInt), + "#0((Int ⨯ #0) + Unit)", +); + +assert.equal( + prettyT(genericFunction), + "(a -> b)", +); + +assert.equal( + prettyT(genericLinkedList), + "#0((a ⨯ #0) + Unit)", +); + + +// comparison + +assert.equal( + compareTypes(listOfSetOfSelf)(listOfSetOfSelf), + 0, + "types should be equal", +); + +assert.equal( + compareTypes(linkedListOfInt)(linkedListOfInt), + 0, + "types should be equal", +); + +assert.notEqual( + compareTypes(linkedListOfInt)(linkedListOfDouble), + 0, + "types should not be equal", +); + +assert.equal( + compareTypes(linkedListOfDouble)(linkedListOfInt) + + compareTypes(linkedListOfInt)(linkedListOfDouble), + 0, + "comparison invariant", +); + +assert.equal( + compareTypes(linkedListOfDouble)(linkedListOfDouble), + 0, + "types should be equal", +) + +assert.notEqual( + compareTypes(linkedListOfDouble)(listOfSetOfSelf), + 0, + "types should not be equal", +); + +assert.equal( + compareTypes(linkedListOfDouble)(listOfSetOfSelf) + + compareTypes(listOfSetOfSelf)(linkedListOfDouble), + 0, + "comparison invariant", +); + +const genericList = makeGeneric(a => lsType(_ => a)); + + +// substitution of type parameters + +assert.equal( + // actual + prettyT(substitute( + genericLinkedList, + new Map([ + [UNBOUND_SYMBOLS[0], Int], + ]) + )), + // expected + "#0((Int ⨯ #0) + Unit)", +); + + +// unification (recursive) + +assert.equal( + // actual + prettyT(unify( + genericLinkedList, + makeGeneric(() => linkedListOfInt) + )), + // expected + "#0((Int ⨯ #0) + Unit)", +) + +assert.equal( + // actual + prettyT(unify( + makeGeneric(() => listOfSetOfSelf), + genericList, + )), + // expected + "#0[{#0}]", +)