From 9e1f679dba94b9107cf8cd698ea948b96010de29 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Thu, 8 May 2025 22:59:01 +0200 Subject: [PATCH] pretty print enhancements + comparison of generic functions --- lib/compare/dynamic.js | 15 ++++++++++- lib/meta/type_constructor.js | 7 ++++- lib/parser/type_parser.js | 4 +-- lib/primitives/dynamic.js | 1 + lib/primitives/unit.js | 6 ++++- lib/structures/dict.js | 17 ++++++++++--- lib/structures/dict.types.js | 5 +++- lib/structures/set.js | 14 +++++++--- lib/structures/set.types.js | 4 ++- lib/structures/type_constructors.js | 9 ------- lib/structures/type_constructors.types.js | 31 +++++++++++++++-------- lib/util/pretty.js | 3 +-- lib/util/rbtree_wrapper.js | 16 ++++++------ lib/util/util.js | 4 +++ tests/recursive_types.js | 2 +- 15 files changed, 93 insertions(+), 45 deletions(-) diff --git a/lib/compare/dynamic.js b/lib/compare/dynamic.js index ef40f3c..6da7145 100644 --- a/lib/compare/dynamic.js +++ b/lib/compare/dynamic.js @@ -1,6 +1,8 @@ import { getInst, getType } from "../primitives/dynamic.js"; import { SymbolBool, SymbolBottom, SymbolByte, SymbolChar, SymbolDouble, SymbolDynamic, SymbolInt, SymbolUUID, SymbolType, SymbolUnit } from "../primitives/primitive_types.js"; +import { UNBOUND_SYMBOLS } from "../primitives/typevars.js"; import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from "../structures/type_constructors.js"; +import { prettyT } from "../util/pretty.js"; import { compareBools, compareNumbers, compareStrings, compareSymbols, compareUnits } from "./primitives.js"; import { compareDicts, compareFunctions, compareLists, compareProducts, compareSets, compareSums } from "./structures.js"; import { compareTypes } from "./type.js"; @@ -9,6 +11,8 @@ export const compareDynamic = x => y => compareTypes(getType(x))(getType(y)) || makeCompareFn(getType(x))(getInst(x))(getInst(y)); +const cannotCompareTypeVarInstances = _ => _ => { throw new Error("Cannot compare instance of type variables"); } + const typeSymbolToCmp = new Map([ [SymbolInt , compareNumbers], [SymbolBool , compareBools], @@ -17,7 +21,7 @@ const typeSymbolToCmp = new Map([ [SymbolChar , compareStrings], [SymbolUnit , compareUnits], [SymbolBottom , _ => _ => { throw new Error("Bottom!"); }], - [SymbolUUID , compareSymbols], + [SymbolUUID , compareSymbols], // [SymbolGenericType, ?] TODO [SymbolType , compareTypes], [SymbolDynamic, compareDynamic], @@ -29,11 +33,20 @@ const typeSymbolToCmp = new Map([ [symbolList , compareLists], [symbolSet , compareSets], [symbolDict , compareDicts], + + // even though we cannot compare typevar instances, + // we still need to give a function or shit will break + // types that contain typevars are never instantiated, + // EXCEPT: + // - generic functions (but for function comparison we don't look at the in/out types) + // - empty list, empty set, empty dict are all generic, but for each type there is only one empty element, so it never needs to be compared to another one + ...UNBOUND_SYMBOLS.map(uuid => [uuid, cannotCompareTypeVarInstances]), ]); export const makeCompareFn = type => { return type.params.reduce( (acc, cur) => acc(makeCompareFn(cur(type))), typeSymbolToCmp.get(type.symbol) + || (() => { throw new Error(`don't know how to compare instances of '${prettyT(type)}'`); })() ); }; diff --git a/lib/meta/type_constructor.js b/lib/meta/type_constructor.js index 825f76a..90b1ba5 100644 --- a/lib/meta/type_constructor.js +++ b/lib/meta/type_constructor.js @@ -1,8 +1,13 @@ import { getHumanReadableName } from "../primitives/symbol.js"; +import { inspect } from "util"; +import { prettyT } from "../util/pretty.js"; const __makeTypeConstructor = (symbol, nAry, params) => { if (nAry === 0) { - return { symbol, params }; + return { + symbol, params, + [inspect.custom](depth, options, inspect){ return options.stylize(prettyT(this), 'null'); }, + }; } // only for debugging, do we give the function a name const fName = `${getHumanReadableName(symbol).toLowerCase()}Type${params.length>0?params.length:''}`; diff --git a/lib/parser/type_parser.js b/lib/parser/type_parser.js index 8dab112..0a35c45 100644 --- a/lib/parser/type_parser.js +++ b/lib/parser/type_parser.js @@ -5,8 +5,8 @@ import { Dynamic } from "../primitives/primitive_types.js"; import { getHumanReadableName } from "../primitives/symbol.js"; import { getSymbol } from "../primitives/type.js"; import { TYPE_VARS } from "../primitives/typevars.js"; -import { dictType, fnType, lsType, prodType, sumType } from "../structures/type_constructors.js"; -import { setType } from "../structures/type_constructors.js"; +import { dictType, fnType, lsType, prodType, sumType } from "../structures/type_constructors.types.js"; +import { setType } from "../structures/type_constructors.types.js"; // A very stupid little parser, that can only parse: // - primitives => simply maps onto types. diff --git a/lib/primitives/dynamic.js b/lib/primitives/dynamic.js index a7edeb6..e00e67e 100644 --- a/lib/primitives/dynamic.js +++ b/lib/primitives/dynamic.js @@ -1,6 +1,7 @@ import { assignFn } from "../generics/generics.js"; export const newDynamic = i => t => ({i, t}); + export const getInst = lnk => lnk.i; export const getType = lnk => lnk.t; diff --git a/lib/primitives/unit.js b/lib/primitives/unit.js index b4634e7..01992fe 100644 --- a/lib/primitives/unit.js +++ b/lib/primitives/unit.js @@ -1,3 +1,7 @@ -export const unit = {}; +import { inspect } from "node:util"; + +export const unit = { + [inspect.custom](depth, options, inspect){ return '()'; } +}; export const eqUnit = _ => _ => true; diff --git a/lib/structures/dict.js b/lib/structures/dict.js index 94b0860..b614831 100644 --- a/lib/structures/dict.js +++ b/lib/structures/dict.js @@ -1,12 +1,21 @@ import { RBTreeWrapper } from "../util/rbtree_wrapper.js"; +import { indent } from "../util/util.js"; import { newProduct } from "./product.js"; import { newLeft, newRight } from "./sum.js"; -export const emptyDict = compareFn => RBTreeWrapper.new((x, y) => compareFn(x)(y)); +// only for debugging +function inspectDict(_depth, options, inspect) { + const entries = []; + this.tree.forEach((key, val) => { entries.push(`${inspect(key, options)} => ${inspect(val, options)}`); }); + return `{\n${indent(entries.join(',\n'),2)}\n}`; +} -export const has = dict => key => dict.tree.get(key) === true; -export const set = dict => key => value => new RBTreeWrapper(dict.tree.remove(key).insert(key, value)); -export const remove = dict => key => new RBTreeWrapper(dict.tree.remove(key)); +export const emptyDict = compareFn => RBTreeWrapper.new((x, y) => compareFn(x)(y), inspectDict); + +export const has = dict => key => dict.tree.get(key) !== undefined; +export const get = dict => key => dict.tree.get(key); +export const set = dict => key => value => new RBTreeWrapper(dict.tree.remove(key).insert(key, value), inspectDict); +export const remove = dict => key => new RBTreeWrapper(dict.tree.remove(key), inspectDict); export const length = dict => dict.tree.length; export const first = dict => dict.tree.begin; diff --git a/lib/structures/dict.types.js b/lib/structures/dict.types.js index 38e586e..c27ff43 100644 --- a/lib/structures/dict.types.js +++ b/lib/structures/dict.types.js @@ -2,9 +2,12 @@ import { makeTypeParser } from "../parser/type_parser.js"; import { makeTypeConstructor } from "../meta/type_constructor.js"; import { emptyDict, first, has, last, length, read, remove, set } from "./dict.js"; -const dictIterator = makeTypeConstructor('DictIterator__d9d175b6bfd1283f00851a99787d0499')(2); +export const symbolDictIterator = 'DictIterator__d9d175b6bfd1283f00851a99787d0499'; + +const dictIterator = makeTypeConstructor(symbolDictIterator)(2); const mkType = makeTypeParser({ + // we invent a bit of syntax for the dict iterator type, which takes two type parameters (key and value) extraInfixOperators: [['|=>|', dictIterator]], }); diff --git a/lib/structures/set.js b/lib/structures/set.js index a535de6..b39c513 100644 --- a/lib/structures/set.js +++ b/lib/structures/set.js @@ -2,13 +2,21 @@ import { newRight } from "./sum.js"; import { newProduct } from "./product.js"; import { unit } from "../primitives/unit.js"; import { RBTreeWrapper } from "../util/rbtree_wrapper.js"; +import { indent } from "../util/util.js"; + +// only for debugging +function inspectSet(_depth, options, inspect) { + const keys = []; + this.tree.forEach((key) => { keys.push(inspect(key, options)); }); + return `{\n${indent(keys.join(',\n'), 2)}\n}`; +} // (a -> a -> Int) -> Set(a) -export const emptySet = compareFn => RBTreeWrapper.new((x, y) => compareFn(x)(y)); +export const emptySet = compareFn => RBTreeWrapper.new((x, y) => compareFn(x)(y), inspectSet); export const has = set => key => set.tree.get(key) === true; -export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWrapper(set.tree.insert(key, true)); -export const remove = set => key => new RBTreeWrapper(set.tree.remove(key)); +export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWrapper(set.tree.insert(key, true), inspectSet); +export const remove = set => key => new RBTreeWrapper(set.tree.remove(key), inspectSet); export const length = set => set.tree.length; export const fold = set => callback => initial => { diff --git a/lib/structures/set.types.js b/lib/structures/set.types.js index 2f50a65..82827a8 100644 --- a/lib/structures/set.types.js +++ b/lib/structures/set.types.js @@ -2,7 +2,9 @@ import { makeTypeParser } from "../parser/type_parser.js"; import { makeTypeConstructor } from "../meta/type_constructor.js"; import { emptySet, has, add, remove, length, first, read, last, fold } from "./set.js"; -const setIterator = makeTypeConstructor('SetIterator__f6b0ddd78ed41c58e5a442f2681da011')(1); +export const symbolSetIterator = 'SetIterator__f6b0ddd78ed41c58e5a442f2681da011'; + +const setIterator = makeTypeConstructor(symbolSetIterator)(1); const mkType = makeTypeParser({ extraBracketOperators: [['<', ['>', setIterator]]], diff --git a/lib/structures/type_constructors.js b/lib/structures/type_constructors.js index 0ec9b65..77c834c 100644 --- a/lib/structures/type_constructors.js +++ b/lib/structures/type_constructors.js @@ -1,17 +1,8 @@ // to break up dependency cycles, type constructors are defined in their own JS module -import { makeTypeConstructor } from "../meta/type_constructor.js"; - export const symbolFunction = "Function__c2433e31fa574a2cb3b6b5d62ac9d4b2"; export const symbolSum = "Sum__89b731efa6344ea0b6a8663a45cf3ea8"; export const symbolProduct = "Product__89351ecdedfb4b05b2a5a6cc0c383e12"; export const symbolList = "List__daa8de8a9047435e96034ec64f2da3a1"; export const symbolSet = "Set__8fef2c1873df4327ac31bd61d2ecf7e0"; export const symbolDict = "Dict__d7158547322549ac9f7f8176aec123dd"; - -export const fnType = makeTypeConstructor(symbolFunction)(2); -export const sumType = makeTypeConstructor(symbolSum)(2); -export const prodType = makeTypeConstructor(symbolProduct)(2); -export const lsType = makeTypeConstructor(symbolList)(1); -export const setType = makeTypeConstructor(symbolSet)(1); -export const dictType = makeTypeConstructor(symbolDict)(2); diff --git a/lib/structures/type_constructors.types.js b/lib/structures/type_constructors.types.js index 81d2c03..d0acf22 100644 --- a/lib/structures/type_constructors.types.js +++ b/lib/structures/type_constructors.types.js @@ -1,8 +1,13 @@ -import { getDefaultTypeParser } from "../parser/type_parser.js"; -import { UUID } from "../primitives/primitive_types.js"; -import { dictType, fnType, lsType, prodType, setType, sumType, symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from "./type_constructors.js"; +import { makeTypeConstructor } from "../meta/type_constructor.js"; +import { Type, UUID } from "../primitives/primitive_types.js"; +import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from "./type_constructors.js"; -const mkType = getDefaultTypeParser(); +export const fnType = makeTypeConstructor(symbolFunction)(2); +export const sumType = makeTypeConstructor(symbolSum)(2); +export const prodType = makeTypeConstructor(symbolProduct)(2); +export const lsType = makeTypeConstructor(symbolList)(1); +export const setType = makeTypeConstructor(symbolSet)(1); +export const dictType = makeTypeConstructor(symbolDict)(2); export const ModuleStructuralSymbols = [ { i: symbolSet , t: UUID }, @@ -13,11 +18,15 @@ export const ModuleStructuralSymbols = [ { i: symbolFunction , t: UUID }, ]; +const unaryTypeConstructor = fnType(_ => Type)(_ => Type); + +const binaryTypeConstructor = fnType(_ => Type)(_ => unaryTypeConstructor); + export const ModuleTypeConstructors = [ - { i: setType , t: mkType("Type -> Type") }, - { i: lsType , t: mkType("Type -> Type") }, - { i: prodType , t: mkType("Type -> Type -> Type") }, - { i: sumType , t: mkType("Type -> Type -> Type") }, - { i: dictType , t: mkType("Type -> Type -> Type") }, - { i: fnType , t: mkType("Type -> Type -> Type") }, -]; \ No newline at end of file + { i: setType , t: unaryTypeConstructor }, + { i: lsType , t: unaryTypeConstructor }, + { i: prodType , t: binaryTypeConstructor }, + { i: sumType , t: binaryTypeConstructor }, + { i: dictType , t: binaryTypeConstructor }, + { i: fnType , t: binaryTypeConstructor }, +]; diff --git a/lib/util/pretty.js b/lib/util/pretty.js index a1d0d1f..9e566e5 100644 --- a/lib/util/pretty.js +++ b/lib/util/pretty.js @@ -1,6 +1,5 @@ import { inspect } from 'node:util'; -import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSum } from '../structures/type_constructors.js'; -import { symbolSet } from "../structures/type_constructors.js"; +import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSum, symbolSet } from '../structures/type_constructors.js'; import { getHumanReadableName } from '../primitives/symbol.js'; import { getSymbol } from '../primitives/type.js'; diff --git a/lib/util/rbtree_wrapper.js b/lib/util/rbtree_wrapper.js index 51f34e5..dc5d067 100644 --- a/lib/util/rbtree_wrapper.js +++ b/lib/util/rbtree_wrapper.js @@ -3,19 +3,19 @@ import createRBTree from "functional-red-black-tree"; import { inspect } from "util"; +function defaultPrintf(depth, options, inspect) { + const entries = []; + this.tree.forEach((key, val) => { entries.push(`${inspect(key)} => ${inspect(val)}`); }); + return `RBTree(${this.tree.length}) {${entries.join(', ')}}`; +} + export class RBTreeWrapper { - constructor(tree) { + constructor(tree, printf = defaultPrintf) { this.tree = tree; + this[inspect.custom] = printf; } static new(compareFn) { return new RBTreeWrapper(createRBTree(compareFn)) } - - // pretty print to console - [inspect.custom](depth, options, inspect) { - const entries = []; - this.tree.forEach((key, val) => { entries.push(`${inspect(key)} => ${inspect(val)}`); }); - return `RBTree(${this.tree.length}) {${entries.join(', ')}}`; - } } diff --git a/lib/util/util.js b/lib/util/util.js index 9bbd332..d993bbd 100644 --- a/lib/util/util.js +++ b/lib/util/util.js @@ -18,3 +18,7 @@ export const memoize = callback => { return result; }; }; + +export const indent = (multiline, n) => { + return multiline.split('\n').map(line => ' '.repeat(n)+line).join('\n'); +} \ No newline at end of file diff --git a/tests/recursive_types.js b/tests/recursive_types.js index 407839c..06f204f 100644 --- a/tests/recursive_types.js +++ b/tests/recursive_types.js @@ -3,7 +3,7 @@ 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 { fnType, lsType, prodType, sumType, setType } from "../lib/structures/type_constructors.types.js"; import { prettyT } from "../lib/util/pretty.js"; // some recursive types: