diff --git a/generics/generics.js b/generics/generics.js index 5515b30..a854f80 100644 --- a/generics/generics.js +++ b/generics/generics.js @@ -1,5 +1,6 @@ import { eqType } from "../type.js"; -import { pretty, zip } from "../util.js"; +import { zip } from "../util/util.js"; +import { pretty } from '../util/pretty.js'; // constructor for generic types // for instance, the type: @@ -22,6 +23,8 @@ export const makeGeneric = callback => { // From the given set of type variables, return only those that occur in the given type. export const occurring = (type, typeVars) => { + console.log("occurring", type, typeVars); + if (typeVars.has(type)) { // type IS a type variable: return new Set([type]); diff --git a/generics/generics.test.js b/generics/generics.test.js index 3054c13..0d967d2 100644 --- a/generics/generics.test.js +++ b/generics/generics.test.js @@ -1,7 +1,7 @@ import { Bool, Int } from "../primitives/types.js"; import { fnType, lsType } from "../structures/types.js"; import { assign, makeGeneric, unify } from "./generics.js"; -import { pretty } from "../util.js"; +import { pretty } from "../util/pretty.js"; // a -> Int const a_to_Int = makeGeneric(a => fnType(a)(Int)); diff --git a/lib/symbol.js b/lib/symbol.js new file mode 100644 index 0000000..1b67d34 --- /dev/null +++ b/lib/symbol.js @@ -0,0 +1,22 @@ +import { constructSymbol, eqSymbol, getName } from "../primitives/symbol.js"; +import { Bool, SymbolT, Type } from "../primitives/types.js"; +import { String } from "../structures/list.js"; +import { typedFnType } from "../structures/types.js"; + +export const ModuleSymbol = {l:[ + {i: SymbolT, t: Type}, + + ...typedFnType(constructSymbol, fnType => + fnType + (String) + (SymbolT) + ), + + ...typedFnType(getName, fnType => + fnType + (SymbolT) + (String) + ), + + ...typedFnType(eqSymbol, fnType => fnType(SymbolT, fnType(SymbolT, Bool))), +]}; diff --git a/lib/type_constructor.js b/lib/type_constructor.js new file mode 100644 index 0000000..99ee760 --- /dev/null +++ b/lib/type_constructor.js @@ -0,0 +1,14 @@ +import { Int, SymbolT, Type } from "../primitives/types.js"; +import { typedFnType } from "../structures/types.js"; +import { makeTypeConstructor } from "../type_constructor.js"; + +// This function and its type signature cannot be in the same file as 'makeTypeConstructor' because then we get an import cycle among JS modules. +export const ModuleTypeConstructor = {l:[ + ...typedFnType(makeTypeConstructor, fnType => + fnType + (SymbolT) + (fnType + (Int) + (Type) + )), +]}; diff --git a/primitives/symbol.js b/primitives/symbol.js new file mode 100644 index 0000000..209c51f --- /dev/null +++ b/primitives/symbol.js @@ -0,0 +1,8 @@ +// The functions are only defined here. For their types, see lib/symbol.js + +// The point of having an explicit constructor for SymbolT, is that we can later swap it out for something that is (de-)serializable. +export const constructSymbol = name => Symbol(name); + +export const getName = symbol => symbol.description; + +export const eqSymbol = a => b => a === b; diff --git a/primitives/types.js b/primitives/types.js index ce2ee79..c798945 100644 --- a/primitives/types.js +++ b/primitives/types.js @@ -1,9 +1,17 @@ // to break up dependency cycles, primitive types are defined in their own JS module -export const Int = { symbol: Symbol('Int') , params: [] }; -export const Bool = { symbol: Symbol('Bool') , params: [] }; -export const Double = { symbol: Symbol('Double'), params: [] }; -export const Byte = { symbol: Symbol('Byte') , params: [] }; -export const Char = { symbol: Symbol('Char') , params: [] }; +import { makeTypeConstructor } from "../type_constructor.js"; +import { constructSymbol } from "./symbol.js"; -export const Type = { symbol: Symbol('Type'), params: [] }; +export const Int = makeTypeConstructor(constructSymbol('Int'), 0); +export const Bool = makeTypeConstructor(constructSymbol('Bool'), 0); +export const Double = makeTypeConstructor(constructSymbol('Double'), 0); +export const Byte = makeTypeConstructor(constructSymbol('Byte'), 0); +export const Char = makeTypeConstructor(constructSymbol('Char'), 0); + +// Unit type has only 1 instance, the empty tuple. +export const Unit = makeTypeConstructor(constructSymbol('Unit'), 0); + +export const SymbolT = makeTypeConstructor(constructSymbol('Symbol'), 0); + +export const Type = makeTypeConstructor(constructSymbol('Type'), 0); diff --git a/primitives/unit.js b/primitives/unit.js new file mode 100644 index 0000000..68fa78c --- /dev/null +++ b/primitives/unit.js @@ -0,0 +1,13 @@ +import { typedFnType } from "../structures/types.js"; +import { Bool, Type, Unit } from "./types.js"; + +const eqUnit = x => y => x === y; + +export const ModuleUnit = {l:[ + {i: {}, t: Unit}, + + {i: Unit, t: Type}, + + // Unit -> Unit -> Bool + ...typedFnType(eqUnit, fnType => fnType(Unit)(fnType(Unit)(Bool))), +]}; diff --git a/scripts/int_or_bool.js b/scripts/int_or_bool.js index 5d6ff67..0a1e1f0 100644 --- a/scripts/int_or_bool.js +++ b/scripts/int_or_bool.js @@ -2,7 +2,7 @@ import { assign, makeGeneric, unify } from "../generics/generics.js"; import { Bool, Int } from "../primitives/types.js"; import { constructorLeft, constructorRight, match } from "../structures/sum.js"; import { fnType, sumType } from "../structures/types.js"; -import { pretty } from "../util.js"; +import { pretty } from '../util/pretty.js'; const IntOrBool = sumType(Int)(Bool); diff --git a/scripts/main.js b/scripts/main.js index 37988f6..af558e9 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -1,10 +1,11 @@ -import { select } from '@inquirer/prompts'; +import { select, number } from '@inquirer/prompts'; import { ModulePoint } from "../lib/point.js"; -import { DefaultMap, pretty, prettyT } from '../util.js'; -import { symbolFunction } from '../structures/types.js'; +import { DefaultMap } from "../util/defaultmap.js"; +import { pretty } from '../util/pretty.js'; +import { isFunction, prettyT } from '../structures/types.js'; import { ModuleStd } from '../stdlib.js'; -import { Type } from "../primitives/types.js"; -import { assign, makeGeneric, unify } from '../generics/generics.js'; +import { Double, Int, Type } from "../primitives/types.js"; +import { eqType } from '../type.js'; class Context { constructor(mod) { @@ -21,7 +22,7 @@ class Context { this.functionsTo = new DefaultMap(() => new Set()); // type to incoming function for (const t of this.instances.m.keys()) { - if (t.symbol === symbolFunction) { + if (isFunction(t)) { // 't' is a function signature for (const fn of this.instances.getdefault(t)) { this.functionsFrom.getdefault(t.params[0], true).add(fn); @@ -67,7 +68,9 @@ let ctx = new Context({l:[ const prettyIT = ({i, t}) => ({ strI: isType(i) ? prettyT(i) : pretty(i), strT: prettyT(t), -}) + // strI: pretty(i), + // strT: pretty(t), +}); const toChoices = ([i, types]) => { return [...types].map(t => { @@ -131,7 +134,7 @@ async function listAllFunctions() { ...[...ctx.types.m.entries()] .filter(([i, types]) => { for (const type of types) { - if (type.symbol === symbolFunction) + if (isFunction(type)) return true; } return false; @@ -192,7 +195,7 @@ async function listAllInstances() { } async function instanceOrTypeOrFnOptions({i, t}) { - if (t.symbol === symbolFunction) { + if (isFunction(t)) { return functionOptions(i, t); } if (isType(i)) { @@ -257,13 +260,29 @@ async function functionOptions(fn, fnT) { async function callFunction(fn, fnT) { const {strI, strT} = prettyIT({i: fn, t: fnT}); const inType = fnT.params[0]; - const choice = await select({ - message: `select parameter for function ${strI} :: ${strT}`, - choices: [ - "(go back)", - ... [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)])), - ], - }); + const choice = await (async () => { + if (eqType(inType)(Int)) { + const n = await number({ + message: `enter an integer (leave empty to go back):`, + step: 1, // only integers + }); + return (n === undefined) ? "(go back)" : {i: BigInt(n), t: Int}; + } + if (eqType(inType)(Double)) { + const n = await number({ + message: `enter a number (leave empty to go back):`, + step: 'any', + }); + return (n === undefined) ? "(go back)" : {i: n, t: Int}; + } + return select({ + message: `select parameter for function ${strI} :: ${strT}`, + choices: [ + "(go back)", + ... [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)])), + ], + }); + })(); if (choice === "(go back)") { return; } @@ -296,7 +315,7 @@ async function instanceOptions(i,t) { async function transform(i, t) { const {strI, strT} = prettyIT({i, t}); - console.log(ctx.functionsFrom.getdefault(t)); + // console.log(ctx.functionsFrom.getdefault(t)); const choice = await select({ message: `choose transformation to perform on ${strI} :: ${strT}`, diff --git a/stdlib.js b/stdlib.js index e25b914..fda246d 100644 --- a/stdlib.js +++ b/stdlib.js @@ -1,8 +1,11 @@ +import { ModuleSymbol } from "./lib/symbol.js"; +import { ModuleTypeConstructor } from "./lib/type_constructor.js"; import { ModuleBool } from "./primitives/bool.js"; import { ModuleByte } from "./primitives/byte.js"; import { ModuleChar } from "./primitives/char.js"; import { ModuleDouble } from "./primitives/double.js"; import { ModuleInt } from "./primitives/int.js"; +import { ModuleUnit } from "./primitives/unit.js"; import { ModuleFunction } from "./structures/function.js"; import { ModuleList } from "./structures/list.js"; import { ModuleProduct } from "./structures/product.js"; @@ -14,12 +17,16 @@ export const ModuleStd = {l:[ ...ModuleType.l, ...ModuleTyped.l, + ...ModuleTypeConstructor.l, + // Primitive types ...ModuleBool.l, ...ModuleByte.l, ...ModuleChar.l, ...ModuleDouble.l, ...ModuleInt.l, + ...ModuleSymbol.l, + ...ModuleUnit.l, // Types that consist of other types ...ModuleFunction.l, diff --git a/structures/list.js b/structures/list.js index c20e8ce..6582ff9 100644 --- a/structures/list.js +++ b/structures/list.js @@ -1,5 +1,5 @@ -import { fnType, typedFnType } from "./types.js"; -import { Type } from "../primitives/types.js"; +import { typedFnType } from "./types.js"; +import { Char, Type } from "../primitives/types.js"; import { Int } from "../primitives/types.js"; import { makeGeneric } from "../generics/generics.js"; import { lsType } from "./types.js"; @@ -10,6 +10,8 @@ const get = ls => i => ls.l[i]; const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)}); const push = ls => elem => ({l:ls.l.concat([elem])}); +export const String = lsType(Char); // alias + export const ModuleList = {l:[ // Type -> Type ...typedFnType(lsType, fnType => @@ -55,4 +57,6 @@ export const ModuleList = {l:[ ) ) ), + + // {i: String, t: Type}, // alias ]}; diff --git a/structures/nominal.js b/structures/nominal.js new file mode 100644 index 0000000..564cea7 --- /dev/null +++ b/structures/nominal.js @@ -0,0 +1,13 @@ +import { Any } from "../typed.js"; +import { String } from "./list.js"; +import { sumType, prodType, fnType } from "./types.js"; + + +export const createNominalADT = symbol => variants => { + +}; + +export const createNominalADTFnType = + fnType + (Any) + (); \ No newline at end of file diff --git a/structures/set.js b/structures/set.js new file mode 100644 index 0000000..48f52ea --- /dev/null +++ b/structures/set.js @@ -0,0 +1,38 @@ +import { setType, typedFnType } from "./types.js"; +import { Bool, Type } from "../primitives/types.js"; +import { makeGeneric } from "../generics/generics.js"; + +// 'normal' implementation +const emptySet = new Set(); +const has = set => elem => set.has(elem); +const add = set => elem => new Set([...set, elem]); + +export const ModuleList = {l:[ + // Type -> Type + ...typedFnType(setType, fnType => + fnType + /* in */ (Type) + /* out */ (Type) + ), + + {i: emptySet, t: makeGeneric(a => setType(a))}, + + ...typedFnType(has, fnType => + makeGeneric(a => + fnType + /* in */ (setType(a)) + /* out */ (fnType + /* in */ (a) + /* out */ (Bool) + ))), + + ...typedFnType(add, fnType => + makeGeneric(a => + fnType + /* in */ (setType(a)) + /* out */ (fnType + /* in */ (a) + /* out */ (setType(a)) + ))), + +]}; diff --git a/structures/types.js b/structures/types.js index 3e7056c..a435d27 100644 --- a/structures/types.js +++ b/structures/types.js @@ -1,21 +1,17 @@ // to break up dependency cycles, type constructors are defined in their own JS module import { Type } from "../primitives/types.js"; -import { DefaultMap } from "../util.js"; - +import { getSymbol, makeTypeConstructor } from "../type_constructor.js"; // Function type // The registry ensures that we never accidentally create more than one JS object for the same function type. // It is a cheap workaround for JS lacking customizable hash-functions and equality-testing-functions. // This same pattern is repeated throughout the code for all non-nullary type constructors (list, sum, product, ...) -export const symbolFunction = Symbol('Function'); -const fnTypeRegistry = new DefaultMap(inType => new DefaultMap(outType => ({ - symbol: symbolFunction, - params: [inType, outType], -}))); -// type constructor -export const fnType = inType => outType => fnTypeRegistry.getdefault(inType, true).getdefault(outType, true); +const symbolFunction = Symbol('Function'); +export const fnType = makeTypeConstructor(symbolFunction, 2); + +export const isFunction = type => getSymbol(type) === symbolFunction; // Convenience function. Wrapper around function below. export const typedFnType = (instance, callback) => { @@ -41,35 +37,48 @@ export const typedFnType2 = callback => { ]; }; - // Sum type -export const symbolSum = Symbol("Sum"); -const sumTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ - symbol: symbolSum, - params: [leftType, rightType], -}))); -// type constructor -export const sumType = leftType => rightType => sumTypeRegistry.getdefault(leftType, true).getdefault(rightType, true); - +const symbolSum = Symbol("Sum"); +export const sumType = makeTypeConstructor(symbolSum, 2); // Product type -export const symbolProduct = Symbol("Product"); -const productTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ - symbol: symbolProduct, - params: [leftType, rightType], -}))); -// type constructor -export const prodType = leftType => rightType => productTypeRegistry.getdefault(leftType, true).getdefault(rightType, true); - +const symbolProduct = Symbol("Product"); +export const prodType = makeTypeConstructor(symbolProduct, 2); // List type -export const symbolList = Symbol('List'); -const listTypeRegistry = new DefaultMap(elementType => ({ - symbol: symbolList, - params: [elementType], -})); -// type constructor -export const lsType = elementType => listTypeRegistry.getdefault(elementType, true); +const symbolList = Symbol('List'); +export const lsType = makeTypeConstructor(symbolList, 1); + +// Set type + +const symbolSet = Symbol('Set'); +export const setType = makeTypeConstructor(symbolSet, 1); + +// Pretty print type +export function prettyT(type) { + if (type.typeVars) { + if (type.typeVars.size > 0) { + return `∀${[...type.typeVars].map(prettyT).join(", ")}: ${prettyT(type.type)}`; + } + return prettyT(type.type); + } + if (type.symbol === symbolFunction) { + return `${prettyT(type.params[0])} -> ${prettyT(type.params[1])}`; + } + if (type.symbol === symbolList) { + return `[${prettyT(type.params[0])}]`; + } + if (type.symbol === symbolProduct) { + return `(${prettyT(type.params[0])}, ${prettyT(type.params[1])})`; + } + if (type.symbol === symbolSum) { + return `(${prettyT(type.params[0])} | ${prettyT(type.params[1])})`; + } + if (type.params.length === 0) { + return type.symbol.description; + } + return `${type.symbol.description}(${type.params.map(prettyT).join(", ")})`; +} diff --git a/structures/versioned.js b/structures/versioned.js new file mode 100644 index 0000000..f7f20a1 --- /dev/null +++ b/structures/versioned.js @@ -0,0 +1,63 @@ +import { makeGeneric } from "../generics/generics.js"; +import { Bool } from "../primitives/types.js"; +import { makeTypeConstructor } from "../type_constructor.js"; +import { getEq } from "../typeclasses/eq.js"; +import { eqDictType } from "../typeclasses/eq_type.js"; +import { fnType, setType } from "./types.js"; + +const symbolVersioned = Symbol("Versioned"); +export const versionedType = makeTypeConstructor(symbolVersioned, 1); + + +export const constructor = parents => value => { + return { parents, alternatives }; +} + +const constructorType = makeGeneric(a => + fnType + (a) + (setType(versionedType(a))) +); + +// const getValue = v => + +const eq = eqDict => vA => vB => { + return getEq(eqDict)(vA.value,vB.value) // compare values + && (vA.parents.symmetricDifference(vB.parents).size === 0); // compare parents +} + +// EqDict a -> Versioned a -> Versioned a -> Bool +const eqVersioned = makeGeneric(a => + fnType + (eqDictType(a)) + (fnType + (versionedType(a)) + (fnType + (versionedType(a)) + (Bool) + ) + ) +); + +// EqDict a -> Versioned a -> Versioned a -> Versioned a -> Versioned a +export const mergeThreeWay = eqDict => vLCA => vA => vB => { + if (eq(eqDict)(vLCA)(vA)) { + return vB; // vB successor of vA + } + if (eq(eqDict)(vLCA)(vB)) { + return vA; // vA successor of vB + } + return mergeConcurrent(vLCA)(vA)(vB); +}; + +export const mergePrimitives = vA => vB => vA.union(vB); + +// Versioned a -> Versioned a -> Versioned a +export const mergePrimitivesType = makeGeneric(a => + fnType + (versionedType(a)) + (fnType + (versionedType(a)) + (versionedType(a)) + ) +); \ No newline at end of file diff --git a/type.js b/type.js index 4d13737..bbd299f 100644 --- a/type.js +++ b/type.js @@ -1,12 +1,10 @@ -import { Bool, Type } from "./primitives/types.js"; -import { typedFnType } from "./structures/types.js"; -import { deepEqual } from "./util.js"; - -export const getSymbol = type => type.symbol; -export const getParams = type => type.params; +import { Bool, SymbolT, Type } from "./primitives/types.js"; +import { isFunction, lsType, typedFnType } from "./structures/types.js"; +import { getSymbol, getParams } from "./type_constructor.js"; +import { deepEqual } from "./util/util.js"; // we can test whether types are equal: -export const eqType = deepEqual; +export const eqType = t1 => t2 => deepEqual(t1, t2); // a module is just a set of typed objects // each 'typed object' is implicitly an instance of TypeLink (defined below) @@ -17,13 +15,8 @@ export const ModuleType = {l:[ // ... // see: https://lean-lang.org/functional_programming_in_lean/functor-applicative-monad/universes.html - // Type :: Type {i: Type, t: Type}, - // ...typedFnType(getSymbol, fnType => fnType({in: Type, out: Int})), - - // ...typedFnType(getParams, fnType => fnType({in: Type, out: lsType(Type)})), - // Type -> Type -> Bool ...typedFnType(eqType, fnType => fnType @@ -32,4 +25,9 @@ export const ModuleType = {l:[ (Type) (Bool) )), + + ...typedFnType(getSymbol, fnType => fnType(Type)(SymbolT)), + ...typedFnType(getParams, fnType => fnType(Type)(lsType(Type))), + + ...typedFnType(isFunction, fnType => fnType(Type)(Bool)), ]}; diff --git a/type_constructor.js b/type_constructor.js new file mode 100644 index 0000000..8fe9925 --- /dev/null +++ b/type_constructor.js @@ -0,0 +1,15 @@ +import { DefaultMap } from "./util/defaultmap.js"; + +// Creates a new nominal type +export const makeTypeConstructor = (name, n_ary, params = []) => { + if (n_ary === 0) { + return { symbol: Symbol(name), params }; + } + else { + const m = new DefaultMap(typeParam => makeTypeConstructor(name, n_ary - 1, params.concat([typeParam]))); + return typeParam => m.getdefault(typeParam, true); + } +}; + +export const getSymbol = type => type.symbol; +export const getParams = type => ({ l: type.params }); diff --git a/typeclasses/eq.js b/typeclasses/eq.js index 14752a1..64e2b6d 100644 --- a/typeclasses/eq.js +++ b/typeclasses/eq.js @@ -1,8 +1,8 @@ import { makeGeneric } from "../generics/generics"; -import { Type } from "../primitives/types"; +import { SymbolT, Type, Unit } from "../primitives/types"; import { typedFnType } from "../structures/types"; import { Bool, Byte, Char, Double, Int } from "../primitives/types"; -import { deepEqual } from "../util"; +import { deepEqual } from "../util/util"; import { eqDictType } from "./eq_type"; export const getEq = numDict => numDict.eq; @@ -32,10 +32,12 @@ const eq = x => y => deepEqual(x,y); const EqDict = {eq}; export const EqInstances = new Map([ - [Int , EqDict], - [Bool , EqDict], - [Double, EqDict], - [Byte , EqDict], - [Char , EqDict], - [Type , EqDict], + [Int , EqDict], + [Bool , EqDict], + [Double , EqDict], + [Byte , EqDict], + [Char , EqDict], + [Unit , EqDict], + [Type , EqDict], + [SymbolT, EqDict], ]); diff --git a/typeclasses/eq_type.js b/typeclasses/eq_type.js index 5055d66..309a46d 100644 --- a/typeclasses/eq_type.js +++ b/typeclasses/eq_type.js @@ -1,4 +1,4 @@ -import { DefaultMap } from "../util.js"; +import { DefaultMap } from "../util/defaultmap.js"; const eqDictSymbol = Symbol('EqDict'); const eqDictTypeRegistry = new DefaultMap(a => ({ diff --git a/typeclasses/num.test.js b/typeclasses/num.test.js index c0ffcbc..e2200ba 100644 --- a/typeclasses/num.test.js +++ b/typeclasses/num.test.js @@ -2,7 +2,7 @@ import { assign } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js"; import { Double, Int } from "../primitives/types.js"; import { fnType } from "../structures/types.js"; -import { pretty } from "../util.js"; +import { pretty } from '../util/pretty.js'; import { getMul, NumInstances } from "./num.js"; import { numDictType } from "./num_type.js"; diff --git a/typeclasses/num_type.js b/typeclasses/num_type.js index 989040e..db6693d 100644 --- a/typeclasses/num_type.js +++ b/typeclasses/num_type.js @@ -1,4 +1,4 @@ -import { DefaultMap } from "../util.js"; +import { DefaultMap } from "../util/defaultmap.js"; const numDictSymbol = Symbol("NumDict"); const numDictTypeRegistry = new DefaultMap(a => ({ diff --git a/util.js b/util.js deleted file mode 100644 index c8c7214..0000000 --- a/util.js +++ /dev/null @@ -1,79 +0,0 @@ -import { inspect } from 'node:util'; -import { symbolFunction, symbolList, symbolProduct, symbolSum } from './structures/types.js'; - -// re-inventing the wheel: -export function deepEqual(a, b) { - if (a === b) return true; // <- shallow equality and primitives - if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) { - return false; - } - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) { - if (!deepEqual(a[i], b[i])) return false; - } - return true; - } - const keysA = Object.keys(a); - const keysB = Object.keys(b); - if (keysA.length !== keysB.length) return false; - for (let key of keysA) { - if (!keysB.includes(key) || !deepEqual(a[key], b[key])) { - return false; - } - } - return true; -} - -// zip two arrays -export function zip(a, b) { - return a.map((k, i) => [k, b[i]]); -} - -export class DefaultMap { - constructor(defaultValue, ...rest) { - this.defaultValue = defaultValue; - this.m = new Map(rest); - } - getdefault(key, addToMapping=false) { - return this.m.get(key) || (() => { - const val = this.defaultValue(key); - if (addToMapping) - this.m.set(key, val); - return val; - })(); - } - entries() { - return this.m.entries(); - } -} - -export function pretty(obj) { - return inspect(obj, {colors: true, depth: null}); -} - -// Pretty print type -export function prettyT(type) { - if (type.typeVars) { - if (type.typeVars.size > 0) { - return `∀${[...type.typeVars].map(prettyT).join(", ")}: ${prettyT(type.type)}`; - } - return prettyT(type.type); - } - if (type.symbol === symbolFunction) { - return `${prettyT(type.params[0])} -> ${prettyT(type.params[1])}`; - } - if (type.symbol === symbolList) { - return `[${prettyT(type.params[0])}]`; - } - if (type.symbol === symbolProduct) { - return `(${prettyT(type.params[0])}, ${prettyT(type.params[1])})`; - } - if (type.symbol === symbolSum) { - return `(${prettyT(type.params[0])} | ${prettyT(type.params[1])})`; - } - if (type.params.length === 0) { - return type.symbol.description; - } - return `${type.symbol.description}(${type.params.map(prettyT).join(", ")})`; -} diff --git a/util/defaultmap.js b/util/defaultmap.js new file mode 100644 index 0000000..6d40c66 --- /dev/null +++ b/util/defaultmap.js @@ -0,0 +1,18 @@ + +export class DefaultMap { + constructor(defaultValue, ...rest) { + this.defaultValue = defaultValue; + this.m = new Map(rest); + } + getdefault(key, addToMapping = false) { + return this.m.get(key) || (() => { + const val = this.defaultValue(key); + if (addToMapping) + this.m.set(key, val); + return val; + })(); + } + entries() { + return this.m.entries(); + } +} diff --git a/util/pretty.js b/util/pretty.js new file mode 100644 index 0000000..d343a79 --- /dev/null +++ b/util/pretty.js @@ -0,0 +1,7 @@ +import { inspect } from 'node:util'; + + +export function pretty(obj) { + return inspect(obj, { colors: true, depth: null }); +} + diff --git a/util/util.js b/util/util.js new file mode 100644 index 0000000..3d6a002 --- /dev/null +++ b/util/util.js @@ -0,0 +1,31 @@ + +// re-inventing the wheel: +export function deepEqual(a, b) { + if (a === b) return true; // <- shallow equality and primitives + if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) { + return false; + } + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (!deepEqual(a[i], b[i])) return false; + } + return true; + } + const keysA = Object.keys(a); + const keysB = Object.keys(b); + if (keysA.length !== keysB.length) return false; + for (let key of keysA) { + if (!keysB.includes(key) || !deepEqual(a[key], b[key])) { + return false; + } + } + return true; +} + +// zip two arrays +export function zip(a, b) { + return a.map((k, i) => [k, b[i]]); +} + +