diff --git a/examples/enum.js b/examples/enum.js index 8ca9301..d58da5d 100644 --- a/examples/enum.js +++ b/examples/enum.js @@ -1,4 +1,4 @@ -import { makeCompareFn } from "../lib/compare/registry.js"; +import { makeCompareFn } from "../lib/compare/dynamic.js"; import { Int, Unit } from "../lib/primitives/primitive_types.js"; import { unit } from "../lib/primitives/unit.js"; import { makeConstructors, makeMatchFn } from "../lib/structures/enum.js"; @@ -7,16 +7,18 @@ import { newProduct } from "../lib/structures/product.js"; import { lsType } from "../lib/structures/type_constructors.js"; import { prettyT } from "../lib/util/pretty.js"; +Error.stackTraceLimit = Infinity; + const variants = [ - newProduct("price")(Int), - newProduct("prices")(lsType(_ => Int)), - newProduct("not_found")(Unit), + newProduct("price")(_ => Int), + newProduct("prices")(_ => lsType(_ => Int)), + newProduct("not_found")(_ => Unit), ]; -const myEnumType = enumType(variants); +const compatibleNestedSumType = enumType(variants); console.log("observe the type that was generated:"); -console.log(" ", prettyT(myEnumType)); +console.log(" ", prettyT(compatibleNestedSumType)); const [newPrice, newPrices, newNotFound] = makeConstructors(variants); @@ -39,7 +41,7 @@ console.log(" ", myEnumToString(price)); console.log(" ", myEnumToString(prices)); console.log(" ", myEnumToString(notFound)); -const compareMyEnum = makeCompareFn(myEnumType); +const compareMyEnum = makeCompareFn(compatibleNestedSumType); console.log("observe the generated compare function in action:"); console.log(" should be smaller ->", compareMyEnum(price)(prices)); diff --git a/examples/generics.js b/examples/generics.js deleted file mode 100644 index c60ba62..0000000 --- a/examples/generics.js +++ /dev/null @@ -1,37 +0,0 @@ -import { assign, makeGeneric, unify } from "../lib/generics/generics.js"; -import { prettyGenT } from "../lib/util/pretty.js"; -import { getDefaultTypeParser } from "../lib/parser/type_parser.js"; - -const mkType = getDefaultTypeParser(); - -console.log("should be: [Bool] -> Int") -console.log(prettyGenT( - unify( - mkType("∀a: (a -> Int)"), - makeGeneric(() => mkType("[Bool] -> Int")), - ) -)); - -console.log("should be: (Bool -> Bool) -> a"); -console.log(prettyGenT( - unify( - mkType("∀a,b: (a -> a) -> b"), - mkType("∀a: (Bool -> Bool) -> a"), - ) -)); - -console.log("should be: [a] -> [a]"); -console.log(prettyGenT( - assign( - mkType("∀a,b: (a -> b) -> [a] -> [b]"), - mkType("∀a: a -> a") - ) -)); - -console.log("should be: [Int] -> Int"); -console.log(prettyGenT( - assign( - mkType("∀a: (a -> Int) -> [a] -> a"), - mkType("∀a: a -> a") - ) -)); diff --git a/examples/parser.js b/examples/parser.js index 0c5ce7c..e69de29 100644 --- a/examples/parser.js +++ b/examples/parser.js @@ -1,16 +0,0 @@ -import { getDefaultTypeParser }from "../lib/parser/type_parser.js"; -import { prettyGenT, prettyT } from "../lib/util/pretty.js"; - -const parse = getDefaultTypeParser(); - -console.log(prettyT(parse("Int"))); // Int - -console.log(prettyT(parse("Int * Bool"))); // (Int ⨯ Bool) - -console.log(prettyT(parse("(((((((Int)))) => ((Bool)))))"))); // (Int => Bool) - -console.log(prettyT(parse("#0((Int * #0) + Unit)"))) // #0((Int ⨯ #0) + Unit) - -console.log(prettyGenT(parse("∀a: #0((a * #0) + Unit"))); // ∀a: #0((a ⨯ #0) + Unit) - -console.log(prettyGenT(parse("∀a,b,c,d: (a*b) + (c*d)"))); // ∀a,b,c,d: ((a ⨯ b) + (c ⨯ d)) diff --git a/examples/recursive_types.js b/examples/recursive_types.js index fae27f8..6a8c7d2 100644 --- a/examples/recursive_types.js +++ b/examples/recursive_types.js @@ -1,8 +1,9 @@ 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 { prettyGenT, prettyT } from "../lib/util/pretty.js"; +import { prettyT } from "../lib/util/pretty.js"; Error.stackTraceLimit = Infinity; @@ -28,8 +29,8 @@ const genericLinkedList = makeGeneric(a => makeLinkedList(a)); console.log(prettyT(listOfSetOfSelf)); // #0[{#0}] console.log(prettyT(linkedListOfInt)); // #0((Int ⨯ #0) + Unit) -console.log(prettyGenT(genericFunction)); // ∀a,b: (a -> b) -console.log(prettyGenT(genericLinkedList)); // ∀a: #0((a ⨯ #0) + Unit) +console.log(prettyT(genericFunction)); // (a -> b) +console.log(prettyT(genericLinkedList)); // #0((a ⨯ #0) + Unit) // comparison @@ -45,16 +46,15 @@ console.log(compareTypes(listOfSetOfSelf)(linkedListOfDouble)) // -1 const genericList = makeGeneric(a => lsType(_ => a)); const intList = lsType(_ => Int); -console.log(prettyGenT(genericList)); // ∀a: [a] +console.log(prettyT(genericList)); // [a] // substitution of type parameters const substituted = substitute( - genericList.type, - new Map([[ - genericList.typeVars.keys().next().value, - Int, - ]]) + genericList, + new Map([ + [TYPE_VARS[0], Int], + ]) ); console.log(prettyT(substituted)); // [Int] @@ -63,16 +63,15 @@ console.log(prettyT(substituted)); // [Int] console.log("recursive substitution") console.log(prettyT(substitute( - genericLinkedList.type, - new Map([[ - genericLinkedList.typeVars.keys().next().value, - Int, - ]]) + genericLinkedList, + new Map([ + [TYPE_VARS[0], Int], + ]) ))); // #0((Int ⨯ #0) + Unit) // unification (simple case) -const {typeVars, type} = unify( +const type = unify( genericList, makeGeneric(() => intList)); @@ -86,7 +85,7 @@ const unified = unify( genericLinkedList, makeGeneric(() => linkedListOfInt)); -console.log(prettyGenT(unified)); // ∀: #0((Int ⨯ #0) + Unit) +console.log(prettyT(unified)); // #0((Int ⨯ #0) + Unit) // unification (strange case) @@ -95,4 +94,4 @@ const unified2 = unify( genericList, ); -console.log(prettyGenT(unified2)); // ∀: #0[{#0}] +console.log(prettyT(unified2)); // #0[{#0}] diff --git a/examples/versioning.js b/examples/versioning.js index f4688c0..c9f862d 100644 --- a/examples/versioning.js +++ b/examples/versioning.js @@ -1,8 +1,7 @@ -import { pretty } from "../util/pretty.js"; -import { newLiteral, transform, read, getReadDependencies, verifyValue } from "../versioning/value.js"; -import { merge, merge2, newSlot, overwrite } from "../versioning/slot.js"; -import { add, emptySet, RBTreeWrapper } from "../structures/set.js"; -import { compareNumbers } from "../compare/primitives.js"; +import { pretty } from "../lib/util/pretty.js"; +import { newLiteral, transform, read, getReadDependencies, verifyValue } from "../lib/versioning/value.js"; +import { merge, merge2, newSlot, overwrite } from "../lib/versioning/slot.js"; +import { compareNumbers } from "../lib/compare/primitives.js"; const inc = x => x + 1; diff --git a/lib/compare/registry.js b/lib/compare/dynamic.js similarity index 52% rename from lib/compare/registry.js rename to lib/compare/dynamic.js index d773d7b..ef40f3c 100644 --- a/lib/compare/registry.js +++ b/lib/compare/dynamic.js @@ -1,22 +1,32 @@ import { getInst, getType } from "../primitives/dynamic.js"; -import { SymbolBool, SymbolChar, SymbolDouble, SymbolInt, SymbolType, SymbolUnit } from "../primitives/primitive_types.js"; -import { symbolDict, symbolList, symbolProduct, symbolSet, symbolSum } from "../structures/type_constructors.js"; -import { compareBools, compareNumbers, compareStrings, compareUnits } from "./primitives.js"; -import { compareDicts, compareLists, compareProducts, compareSets, compareSums } from "./structures.js"; +import { SymbolBool, SymbolBottom, SymbolByte, SymbolChar, SymbolDouble, SymbolDynamic, SymbolInt, SymbolUUID, SymbolType, SymbolUnit } from "../primitives/primitive_types.js"; +import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from "../structures/type_constructors.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"; +export const compareDynamic = x => y => + compareTypes(getType(x))(getType(y)) + || makeCompareFn(getType(x))(getInst(x))(getInst(y)); + const typeSymbolToCmp = new Map([ [SymbolInt , compareNumbers], - [SymbolChar , compareStrings], - [SymbolDouble , compareNumbers], [SymbolBool , compareBools], + [SymbolDouble , compareNumbers], + [SymbolByte , compareNumbers], + [SymbolChar , compareStrings], [SymbolUnit , compareUnits], + [SymbolBottom , _ => _ => { throw new Error("Bottom!"); }], + [SymbolUUID , compareSymbols], + // [SymbolGenericType, ?] TODO [SymbolType , compareTypes], + [SymbolDynamic, compareDynamic], // these functions take extra comparison callbacks: - [symbolList , compareLists], - [symbolProduct , compareProducts], + [symbolFunction, compareFunctions], [symbolSum , compareSums], + [symbolProduct , compareProducts], + [symbolList , compareLists], [symbolSet , compareSets], [symbolDict , compareDicts], ]); @@ -27,7 +37,3 @@ export const makeCompareFn = type => { typeSymbolToCmp.get(type.symbol) ); }; - -export const compareDynamic = x => y => - compareTypes(getType(x))(getType(y)) - || makeCompareFn(getType(x))(getInst(x))(getInst(y)); diff --git a/lib/compare/dynamic.types.js b/lib/compare/dynamic.types.js new file mode 100644 index 0000000..c045093 --- /dev/null +++ b/lib/compare/dynamic.types.js @@ -0,0 +1,9 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { compareDynamic, makeCompareFn } from "./dynamic.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleCompareDynamic = [ + {i: makeCompareFn , t: mkType("∀a: Type -> a -> a -> Int")}, + {i: compareDynamic, t: mkType("Dynamic -> Dynamic -> Int")}, +]; \ No newline at end of file diff --git a/lib/compare/structures.js b/lib/compare/structures.js index a12c7dc..77c45ba 100644 --- a/lib/compare/structures.js +++ b/lib/compare/structures.js @@ -1,12 +1,17 @@ // Total ordering of composed types -import { compareNumbers } from "./primitives.js" +import { compareNumbers, compareStrings } from "./primitives.js" import { get, length as lengthLs } from "../structures/list.js"; import { read as readSet, length as lengthSet, first as firstSet } from "../structures/set.js"; import { read as readDict, length as lengthDict, first as firstDict } from "../structures/dict.js"; import { getLeft, getRight } from "../structures/product.js"; import { match } from "../structures/sum.js"; +export const compareFunctions = _compareInput => _compareOutput => x => y => { + // dirty but does the job + return compareStrings(x.toString())(y.toString()); +} + export const compareLists = compareElems => x => y => { return compareNumbers(lengthLs(x))(lengthLs(y)) || (() => { diff --git a/lib/generics/generics.js b/lib/generics/generics.js index d8ee7e4..d6a4e5b 100644 --- a/lib/generics/generics.js +++ b/lib/generics/generics.js @@ -1,64 +1,57 @@ -import { eqType } from "../primitives/type.js"; +import { eqType, getSymbol } from "../primitives/type.js"; import { zip } from "../util/util.js"; import { pretty, prettyT } from '../util/pretty.js'; +import { isTypeVar, TYPE_VARS } from "../primitives/typevars.js"; -// constructor for generic types +// helper for creating generic types // for instance, the type: // ∀a: a -> a -> Bool // is created by // makeGeneric(a => fnType(() => a)(() => fnType(() => a)(() => Bool))) export const makeGeneric = callback => { // type variables to make available: - const typeVars = ['a', 'b', 'c', 'd', 'e'].map(Symbol); - const type = callback(...typeVars); - return onlyOccurring(type, new Set(typeVars)); + const type = callback(...TYPE_VARS); + return type; }; -export const onlyOccurring = (type, typeVars) => ({ - typeVars: occurring(type, typeVars), - type, -}); - -const __occurring = state => typeVars => type => { - if (typeVars.has(type)) { - return new Set([type]); +const _occurring = stack => type => { + if (isTypeVar(type)) { + return new Set([getSymbol(type)]); } - const tag = state.nextTag++; - state.seen.add(tag); + const tag = stack.length; + const newStack = [...stack, tag]; return new Set(type.params.flatMap(p => { const innerType = p(tag); - if (state.seen.has(innerType)) { + if (newStack.includes(innerType)) { return []; // no endless recursion! } - return [...__occurring(state)(typeVars)(innerType)]; + return [..._occurring(newStack)(innerType)]; })); }; -// From the given set of type variables, return only those that occur in the given type. -export const occurring = (type, typeVars) => { - return __occurring({nextTag:0, seen: new Set()})(typeVars)(type); -}; +// Get set of type variables in type. +export const occurring = _occurring([]); // Merge 2 substitution-mappings, uni-directional. const mergeOneWay = (m1, m2) => { const m1copy = new Map(m1); const m2copy = new Map(m2); - for (const [var1, typ1] of m1copy.entries()) { - if (m2copy.has(typ1)) { + for (const [symbol1, typ1] of m1copy) { + if (m2copy.has(getSymbol(typ1))) { // typ1 is a typeVar for which we also have a substitution // -> fold substitutions - m1copy.set(var1, m2.get(typ1)); - m2copy.delete(typ1); - return [false, m1copy, m2copy, new Set([typ1])]; + m1copy.set(symbol1, m2.get(getSymbol(typ1))); + m2copy.delete(getSymbol(typ1)); + return [false, m1copy, m2copy]; } } - return [true, m1copy, m2copy, new Set()]; // stable + return [true, m1copy, m2copy]; // stable }; const checkConflict = (m1, m2) => { - for (const [var1, typ1] of m1) { - if (m2.has(var1)) { - const other = m2.get(var1); + for (const [symbol1, typ1] of m1) { + if (m2.has(symbol1)) { + const other = m2.get(symbol1); if (!eqType(typ1, other)) { throw new Error(`conflicting substitution: ${pretty(typ1)}vs. ${pretty(other)}`); } @@ -73,21 +66,17 @@ export const mergeTwoWay = (m1, m2) => { // checkConflict(m2, m1); // <- don't think this is necessary... // actually merge let stable = false; - let deletions = new Set(); while (!stable) { - let d; // notice we swap m2 and m1, so the rewriting can happen both ways: - [stable, m2, m1, d] = mergeOneWay(m1, m2); - deletions = deletions.union(d); + [stable, m2, m1] = mergeOneWay(m1, m2); } - const result = { - substitutions: new Map([...m1, ...m2]), - deletions, // deleted type variables - }; + const result = new Map([...m1, ...m2]); // console.log("mergeTwoWay result =", result); return result; }; +export class UnifyError extends Error {} + // Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog (hence the function name): // https://www.dai.ed.ac.uk/groups/ssp/bookpages/quickprolog/node12.html // @@ -95,35 +84,29 @@ export const mergeTwoWay = (m1, m2) => { // typeVars: all the type variables in both fType and aType // fType, aType: generic types to unify // fStack, aStack: internal use. -const __unify = (typeVars, fType, aType, fStack=[], aStack=[]) => { +const __unify = (fType, aType, fStack=[], aStack=[]) => { // console.log("__unify", {typeVars, fType: prettyT(fType), aType: prettyT(aType), fStack, aStack}); - if (typeVars.has(fType)) { + if (isTypeVar(fType)) { // simplest case: formalType is a type paramater // => substitute with actualType // console.log(`assign ${prettyT(aType)} to ${prettyT(fType)}`); return { - substitutions: new Map([[fType, aType]]), - genericType: { - typeVars: typeVars.difference(new Set([fType])), - type: aType, - }, + substitutions: new Map([[getSymbol(fType), aType]]), + type: aType, }; } - if (typeVars.has(aType)) { + if (isTypeVar(aType)) { // same as above, but in the other direction // console.log(`assign ${prettyT(fType)} to ${prettyT(aType)}`); return { - substitutions: new Map([[aType, fType]]), - genericType: { - typeVars: typeVars.difference(new Set([aType])), - type: fType, - }, + substitutions: new Map([[getSymbol(aType), fType]]), + type: fType, }; } // recursively unify if (fType.symbol !== aType.symbol) { - throw new Error(`cannot unify ${prettyT(fType)} and ${prettyT(aType)}`); + throw new UnifyError(`cannot unify ${prettyT(fType)} and ${prettyT(aType)}`); } const fTag = fStack.length; @@ -137,106 +120,79 @@ const __unify = (typeVars, fType, aType, fStack=[], aStack=[]) => { // type recursively points to an enclosing type that we've already seen if (fStack[fParam] !== aStack[aParam]) { // note that both are also allowed not to be mapped (undefined) - throw new Error("cannot unify: types differ in their recursion"); + throw new UnifyError("cannot unify: types differ in their recursion"); } if (fStack[fParam] !== undefined) { const type = fStack[fParam]; return () => ({ substitutions: new Map(), - genericType: { - typeVars, - type, - }, + type, }); } - return parent => __unify(typeVars, fParam, aParam, + return parent => __unify(fParam, aParam, [...fStack, parent], [...aStack, parent]); }); const unifiedParams = unifications.map(getParam => { - return parent => getParam(parent).genericType.type; + return parent => getParam(parent).type; }); - const type = { - symbol: fType.symbol, - params: unifiedParams, - }; - const [unifiedSubstitutions, unifiedTypeVars] = unifications.reduce((acc, getParam) => { + const unifiedSubstitutions = unifications.reduce((acc, getParam) => { const self = Symbol(); // dirty, just need something unique - const {substitutions, deletions} = mergeTwoWay(acc[0], getParam(self).substitutions); - return [substitutions, acc[1] - .difference(substitutions) - .difference(deletions)]; - }, [new Map(), typeVars]); + const paramSubstitutions = getParam(self).substitutions; + const substitutions = mergeTwoWay(acc, paramSubstitutions); + return substitutions; + }, new Map()); return { substitutions: unifiedSubstitutions, - genericType: { - typeVars: unifiedTypeVars, - type, + type: { + symbol: fType.symbol, + params: unifiedParams, }, }; }; -export const unify = (fGenericType, aGenericType) => { - let allTypeVars; - [allTypeVars, fGenericType, aGenericType] = safeUnionTypeVars(fGenericType, aGenericType); - const {genericType, substitutions} = __unify(allTypeVars, fGenericType.type, aGenericType.type); +export const unify = (fType, aType) => { + [fType, aType] = recomputeTypeVars([fType, aType]); + const {type, substitutions} = __unify(fType, aType); // console.log('unification complete! substitutions:', substitutions); - return recomputeTypeVars(genericType); + return recomputeTypeVars([type])[0]; }; export const substitute = (type, substitutions, stack=[]) => { // console.log('substitute...', {type, substitutions, stack}); - return substitutions.get(type) + return substitutions.get(getSymbol(type)) || { - symbol: type.symbol, + symbol: getSymbol(type), params: type.params.map(getParam => parent => { const param = getParam(stack.length); - const have = stack[param]; - return (have !== undefined) - ? have - : substitute(param, substitutions, [...stack, parent]); + return stack[param] + || substitute(param, substitutions, [...stack, parent]); }), }; }; -export const assign = (genFnType, paramType) => { - let allTypeVars; - [allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType); - const [inType, outType] = genFnType.type.params; - const {substitutions} = __unify(allTypeVars, inType(genFnType.type), paramType.type); - const substitutedOutType = substitute(outType(genFnType.type), substitutions); - return recomputeTypeVars(onlyOccurring(substitutedOutType, allTypeVars)); +export const assignFn = (funType, paramType) => { + [funType, paramType] = recomputeTypeVars([funType, paramType]); + // console.log(prettyT(funType), prettyT(paramType)); + const [inType, outType] = funType.params.map(p => p(funType)); + const {substitutions} = __unify(inType, paramType); + // console.log(substitutions, prettyT(outType)); + const substitutedFnType = substitute(outType, substitutions); + return recomputeTypeVars([substitutedFnType])[0]; }; -export const assignFn = (genFnType, paramType) => { - let allTypeVars; - [allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType); - const [inType] = genFnType.type.params; - const {substitutions} = __unify(allTypeVars, inType, paramType.type); - const substitutedFnType = substitute(genFnType.type, substitutions); - return recomputeTypeVars(onlyOccurring(substitutedFnType, allTypeVars)); -}; - -export const recomputeTypeVars = (genType) => { - const newTypeVars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'].map(Symbol); +// Ensures that no type variables overlap +export const recomputeTypeVars = types => { let nextIdx = 0; - const subst = new Map(); - for (const typeVarA of genType.typeVars) { - subst.set(typeVarA, newTypeVars[nextIdx++]); - } - const substType = { - typeVars: new Set(subst.values()), - type: substitute(genType.type, subst), - }; - return substType; -}; - -export const safeUnionTypeVars = (genTypeA, genTypeB) => { - const substTypeA = recomputeTypeVars(genTypeA); - const substTypeB = recomputeTypeVars(genTypeB); - const allTypeVars = substTypeA.typeVars.union(substTypeB.typeVars); - return [allTypeVars, substTypeA, substTypeB]; -}; + return types.map(type => { + const substitutions = new Map(); + const typeVars = occurring(type); + for (const typeVar of typeVars) { + substitutions.set(typeVar, TYPE_VARS[nextIdx++]); + } + return substitute(type, substitutions); + }); +} diff --git a/lib/parser/type_parser.js b/lib/parser/type_parser.js index 3ba9b07..9378f1f 100644 --- a/lib/parser/type_parser.js +++ b/lib/parser/type_parser.js @@ -1,7 +1,10 @@ // A simple, hacked-together recursive parser for types. -import { Bool, Char, Double, Int, SymbolT, Type, Unit } from "../primitives/primitive_types.js"; +import { Bool, Char, Double, Int, UUID, Type, Unit } from "../primitives/primitive_types.js"; 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"; @@ -11,12 +14,6 @@ export const makeTypeParser = ({ extraBracketOperators=[], extraInfixOperators=[], }) => { - const a = Symbol('a'); - const b = Symbol('b'); - const c = Symbol('c'); - const d = Symbol('d'); - const e = Symbol('e'); - const primitives = new Map([ ['Int', Int], ['Double', Double], @@ -27,12 +24,9 @@ export const makeTypeParser = ({ ['Unit', Unit], ['Type', Type], ['Dynamic', Dynamic], - ['SymbolT', SymbolT], - ['a', a], - ['b', b], - ['c', c], - ['d', d], - ['e', e], + ['UUID', UUID], + + ...TYPE_VARS.map(type => [getHumanReadableName(getSymbol(type)), type]), ...extraPrimitives, ]); @@ -41,11 +35,7 @@ export const makeTypeParser = ({ ['(', [')', null]], ['[', [']', lsType]], ['{', ['}', setType]], - - // can only occur at beginning - // we use these to extract the type variables - ['∀', [':', null]], - + ...extraBracketOperators, ]); @@ -187,13 +177,6 @@ export const makeTypeParser = ({ const parse = expr => { const tokens = tokenize(expr); - if (tokens[0] === '∀') { - // generic type - const [typeVarTokens, _, rest] = consumeGroup(tokens); - const typeVars = [].concat(__parse(typeVarTokens)) - const type = __parse(rest); - return { typeVars, type }; - } return __parse(tokens); } diff --git a/lib/primitives/primitive_types.js b/lib/primitives/primitive_types.js index c4b7f88..8e2f95d 100644 --- a/lib/primitives/primitive_types.js +++ b/lib/primitives/primitive_types.js @@ -9,10 +9,9 @@ export const SymbolByte = "Byte__bf9e8453cd554e81971880ba33dc9f27"; export const SymbolChar = "Char__e47159519d3345119336b751fc8da1de"; export const SymbolUnit = "Unit__a70ca021c32a4036a594d332aedfb029"; export const SymbolBottom = "⊥__95beece951bc457781be8c5481d35dcc"; -export const SymbolSymbol = "Symbol__f67c077430e04e4fa40ed2e2b2a3040d"; +export const SymbolUUID = "UUID__f67c077430e04e4fa40ed2e2b2a3040d"; export const SymbolType = "Type__fdbea309d66f49b483b0dd4ceb785f7d"; export const SymbolTop = "⊤__38709c3c0039468782103256d4730d1f"; -export const SymbolGenericType = "GenericType__e9d8010b82f64206afa83d0c163df5d2"; export const SymbolDynamic = "Dynamic__3c16c415dba94228ada37dc9d446f54f"; export const Int = makeTypeConstructor(SymbolInt)(0); @@ -27,13 +26,11 @@ export const Unit = makeTypeConstructor(SymbolUnit)(0); // Bottom type has no instances. export const Bottom = makeTypeConstructor(SymbolBottom)(0); -export const SymbolT = makeTypeConstructor(SymbolSymbol)(0); +export const UUID = makeTypeConstructor(SymbolUUID)(0); // Types are typed by Top export const Type = makeTypeConstructor(SymbolType)(0); -export const GenericType = makeTypeConstructor(SymbolGenericType)(0); - // Everything is typed by Top export const Top = makeTypeConstructor(SymbolTop)(0);// A type-link, connecting a value to its Type. diff --git a/lib/primitives/primitive_types.types.js b/lib/primitives/primitive_types.types.js index c5a49d1..6bc2bca 100644 --- a/lib/primitives/primitive_types.types.js +++ b/lib/primitives/primitive_types.types.js @@ -1,18 +1,17 @@ -import { SymbolInt, SymbolT, SymbolBool, SymbolDouble, SymbolByte, SymbolChar, SymbolUnit, SymbolBottom, SymbolSymbol, SymbolType, SymbolGenericType, SymbolTop, Type, Int, Bool, Double, Byte, Char, Unit, Bottom, GenericType, Top, SymbolDynamic, Dynamic } from "./primitive_types.js"; +import { SymbolInt, UUID, SymbolBool, SymbolDouble, SymbolByte, SymbolChar, SymbolUnit, SymbolBottom, SymbolUUID, SymbolType, SymbolTop, Type, Int, Bool, Double, Byte, Char, Unit, Bottom, Top, SymbolDynamic, Dynamic } from "./primitive_types.js"; export const ModulePrimitiveSymbols = [ - { i: SymbolInt , t: SymbolT }, - { i: SymbolBool , t: SymbolT }, - { i: SymbolDouble , t: SymbolT }, - { i: SymbolByte , t: SymbolT }, - { i: SymbolChar , t: SymbolT }, - { i: SymbolUnit , t: SymbolT }, - { i: SymbolBottom , t: SymbolT }, - { i: SymbolSymbol , t: SymbolT }, - { i: SymbolType , t: SymbolT }, - { i: SymbolGenericType , t: SymbolT }, - { i: SymbolTop , t: SymbolT }, - { i: SymbolDynamic , t: SymbolT }, + { i: SymbolInt , t: UUID }, + { i: SymbolBool , t: UUID }, + { i: SymbolDouble , t: UUID }, + { i: SymbolByte , t: UUID }, + { i: SymbolChar , t: UUID }, + { i: SymbolUnit , t: UUID }, + { i: SymbolBottom , t: UUID }, + { i: SymbolUUID , t: UUID }, + { i: SymbolType , t: UUID }, + { i: SymbolTop , t: UUID }, + { i: SymbolDynamic , t: UUID }, ]; export const ModulePrimitiveTypes = [ @@ -23,9 +22,8 @@ export const ModulePrimitiveTypes = [ { i: Char , t: Type }, { i: Unit , t: Type }, { i: Bottom , t: Type }, - { i: SymbolT , t: Type }, + { i: UUID , t: Type }, { i: Type , t: Type }, - { i: GenericType , t: Type }, { i: Top , t: Type }, { i: Dynamic , t: Type }, ]; diff --git a/lib/primitives/typevars.js b/lib/primitives/typevars.js new file mode 100644 index 0000000..12a854a --- /dev/null +++ b/lib/primitives/typevars.js @@ -0,0 +1,43 @@ +import { makeTypeConstructor } from "../meta/type_constructor.js"; +import { memoize } from "../util/util.js"; +import { getSymbol } from "./type.js"; + +export const UNBOUND_SYMBOLS = [ + "a__00000000000000000000000000000000", + "b__00000000000000000000000000000000", + "c__00000000000000000000000000000000", + "d__00000000000000000000000000000000", + "e__00000000000000000000000000000000", + "f__00000000000000000000000000000000", + "g__00000000000000000000000000000000", + "h__00000000000000000000000000000000", + "i__00000000000000000000000000000000", + "j__00000000000000000000000000000000", + "k__00000000000000000000000000000000", + "l__00000000000000000000000000000000", + "m__00000000000000000000000000000000", + "n__00000000000000000000000000000000", + "o__00000000000000000000000000000000", + "p__00000000000000000000000000000000", + "q__00000000000000000000000000000000", + "r__00000000000000000000000000000000", + "s__00000000000000000000000000000000", + "t__00000000000000000000000000000000", + "u__00000000000000000000000000000000", + "v__00000000000000000000000000000000", + "w__00000000000000000000000000000000", + "x__00000000000000000000000000000000", + "y__00000000000000000000000000000000", + "z__00000000000000000000000000000000", +]; + +// Type variables are just like nominal types. +export const TYPE_VARS = UNBOUND_SYMBOLS.map(symbol => makeTypeConstructor(symbol)(0)); + +// Turn list into Set for faster lookup. +const unbound_set = memoize(() => new Set(UNBOUND_SYMBOLS)); + +// What makes a type variable a type variable, is its symbol occurring in the above list. +export const isTypeVar = type => { + return unbound_set().has(getSymbol(type)); +}; diff --git a/lib/structures/enum.js b/lib/structures/enum.js index cfc6e3c..0af542b 100644 --- a/lib/structures/enum.js +++ b/lib/structures/enum.js @@ -11,7 +11,7 @@ const eatParameters = (numParams, result) => { export const makeMatchFn = variants => { if (variants.length === 0) { - return undefined; + throw new Error("Bottom!"); } const [_, ...remainingVariants] = variants; return sum => handler => { @@ -34,4 +34,4 @@ export const makeConstructors = variants => { ...makeConstructors(remainingVariants).map(ctor => ({[ctor.name]: val => newRight(ctor(val))}[ctor.name])), ]; -} +}; diff --git a/lib/structures/enum.types.js b/lib/structures/enum.types.js index 21436d9..bb17875 100644 --- a/lib/structures/enum.types.js +++ b/lib/structures/enum.types.js @@ -1,6 +1,10 @@ -import { Bottom } from "../primitives/primitive_types.js"; +import { makeCompareFn } from "../compare/dynamic.js"; +import { makeGeneric } from "../generics/generics.js"; +import { newDynamic } from "../primitives/dynamic.js"; +import { Bottom, Type } from "../primitives/primitive_types.js"; +import { makeConstructors, makeMatchFn } from "./enum.js"; import { getRight } from "./product.js"; -import { sumType } from "./type_constructors.js"; +import { fnType, sumType } from "./type_constructors.js"; // 'variants' is an array of (name: string, type: Type) pairs. // e.g., the list of variants: @@ -10,11 +14,67 @@ import { sumType } from "./type_constructors.js"; // results in the type: // (Int | ([Int] | (Unit | ⊥))) -export const enumType = variants => { +const _enumType = rootSelf => variants => { + // console.log("enumType..", variants); if (variants.length === 0) { return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated) } const [variant, ...rest] = variants; const variantType = getRight(variant); - return sumType(() => variantType)(() => enumType(rest)); + return sumType + (self => { + // console.log("requested left type (of enumType)") + return variantType(rootSelf || self); + }) + (self => { + // console.log("requested right type (of enumType)") + return _enumType(self)(rest) + }); }; + +export const enumType = _enumType(null); + +export const makeConstructorTypes = type => variants => { + return variants.map(variant => { + const variantType = getRight(variant); + return fnType(_ => variantType)(_ => type); + }); +} + +export const makeMatchFnType = type => variants => { + return makeGeneric(resultType => + fnType + (_ => type) + (_ => handlerType(resultType)(type)(variants))); +} + +const handlerType = resultType => type => variants => { + if (variants.length === 0) { + return resultType; + } + const [variant, ...rest] = variants; + const variantType = getRight(variant); + return fnType + (_ => fnType(_ => variantType)(_ => resultType)), // handler + (_ => handlerType(resultType)(type)(rest)); // rest +} + +export const makeModuleEnum = type => variants => { + const ctors = makeConstructors(variants); + const ctorTypes = makeConstructorTypes(type)(variants); + const matchFn = makeMatchFn(variants); + const matchFnType = makeMatchFnType(type)(variants); + const module = [ + // newDynamic(type)(Type), + + // constructors: + ...zip(ctors, ctorTypes) + .map(([ctor, ctorType]) => newDynamic(ctor)(ctorType)), + + // match-function: + newDynamic(matchFn, matchFnType), + + // compare-function: + newDynamic(makeCompareFn(enumType(variants))) + ]; +} \ No newline at end of file diff --git a/lib/structures/set.js b/lib/structures/set.js index 46ce176..a535de6 100644 --- a/lib/structures/set.js +++ b/lib/structures/set.js @@ -11,6 +11,15 @@ export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWr export const remove = set => key => new RBTreeWrapper(set.tree.remove(key)); export const length = set => set.tree.length; +export const fold = set => callback => initial => { + let acc = initial; + let iter = set.tree.begin; + while (iter !== undefined && iter.valid) { + acc = callback(acc, iter.key); + } + return acc; +}; + export const first = set => set.tree.begin; export const last = set => set.tree.end; diff --git a/lib/structures/set.types.js b/lib/structures/set.types.js index 20e39da..32fe7af 100644 --- a/lib/structures/set.types.js +++ b/lib/structures/set.types.js @@ -1,6 +1,6 @@ import { makeTypeParser } from "../parser/type_parser.js"; import { makeTypeConstructor } from "../meta/type_constructor.js"; -import { emptySet, has, add, remove, length, first, read, last } from "./set.js"; +import { emptySet, has, add, remove, length, first, read, last, fold } from "./set.js"; const setIterator = makeTypeConstructor('SetIterator__f6b0ddd78ed41c58e5a442f2681da011')(1); @@ -14,6 +14,7 @@ export const ModuleSet = [ { i: add , t: mkType("∀a: {a} -> a -> {a}")}, { i: remove , t: mkType("∀a: {a} -> a -> {a}")}, { i: length , t: mkType("∀a: {a} -> Int")}, + { i: fold , t: mkType("∀a,b: {a} -> (b -> a -> b) -> b")}, { i: first , t: mkType("∀a: {a} -> ")}, { i: last , t: mkType("∀a: {a} -> ")}, { i: read , t: mkType("∀a: -> (Unit + (a * ))")}, diff --git a/lib/structures/struct.types.js b/lib/structures/struct.types.js index 2946f65..88b149c 100644 --- a/lib/structures/struct.types.js +++ b/lib/structures/struct.types.js @@ -13,40 +13,56 @@ import { fnType, prodType } from "./type_constructors.js"; // [{l: "x", r: Double}, {l: "y", r: Double}] // results in the type (Double × (Double × Unit)) -export const structType = fieldTypes => { - if (fieldTypes.length === 0) { +export const structType = (fields, rootSelf) => { + // console.log("structType..", fields); + if (fields.length === 0) { return Unit; } - const [fieldType, ...rest] = fieldTypes; - return prodType(_ => fieldType)(_ => structType(rest)); + const [field, ...rest] = fields; + const fieldType = getRight(field); + return prodType + (self => { + // console.log("requested left type (of structType)") + return fieldType(rootSelf || self); + }) + (self => { + // console.log("requested right type (of structType)") + return structType(rest, self); + }); }; -export const makeConstructorType = fieldTypes => { - if (fieldTypes.length === 0) { - return structType(fieldTypes); +export const makeConstructorType = type => fields => { + if (fields.length === 0) { + return type; + // return structType(fields); } - const [fieldType, ...rest] = fieldTypes; - return fnType(_ => fieldType)(_ => makeConstructorType(rest)); + const [field, ...rest] = fields; + const fieldType = getRight(field); + return fnType(_ => fieldType)(_ => makeConstructorType(type, rest)); }; -export const makeGettersTypes = fieldTypes => { - const type = structType(fieldTypes); - return fieldTypes.map(fieldType => { +export const makeGettersTypes = type => fields => { + // const type = structType(fields); + return fields.map(field => { + const fieldType = getRight(field); return fnType(_ => type)(_ => fieldType); }); }; -export const makeModuleStruct = fields => { +export const makeModuleStruct = type => fields => { const fieldNames = map(fields)(getLeft); - const fieldTypes = map(fields)(getRight); - const type = structType(fieldTypes); + // const type = structType(fields); const ctor = makeConstructor(fields.length); - const ctorType = makeConstructorType(fieldTypes); - const getterTypes = makeGettersTypes(fieldTypes); + const ctorType = makeConstructorType(fields); + const getterTypes = makeGettersTypes(fields); const getters = makeGetters(fieldNames); const module = [ - {i: type, t: Type}, + // {i: type, t: Type}, + + // constructor {i: ctor, t: ctorType}, + + // getters: ...zip(getters, getterTypes) .map(([getter, getterType]) => newDynamic(getter)(getterType)), ]; diff --git a/lib/structures/type_constructors.types.js b/lib/structures/type_constructors.types.js index 04c6eb6..81d2c03 100644 --- a/lib/structures/type_constructors.types.js +++ b/lib/structures/type_constructors.types.js @@ -1,16 +1,16 @@ import { getDefaultTypeParser } from "../parser/type_parser.js"; -import { SymbolT } from "../primitives/primitive_types.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"; const mkType = getDefaultTypeParser(); export const ModuleStructuralSymbols = [ - { i: symbolSet , t: SymbolT }, - { i: symbolList , t: SymbolT }, - { i: symbolProduct , t: SymbolT }, - { i: symbolSum , t: SymbolT }, - { i: symbolDict , t: SymbolT }, - { i: symbolFunction , t: SymbolT }, + { i: symbolSet , t: UUID }, + { i: symbolList , t: UUID }, + { i: symbolProduct , t: UUID }, + { i: symbolSum , t: UUID }, + { i: symbolDict , t: UUID }, + { i: symbolFunction , t: UUID }, ]; export const ModuleTypeConstructors = [ diff --git a/lib/util/pretty.js b/lib/util/pretty.js index b8e09cf..a1d0d1f 100644 --- a/lib/util/pretty.js +++ b/lib/util/pretty.js @@ -1,36 +1,30 @@ import { inspect } from 'node:util'; import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSum } from '../structures/type_constructors.js'; import { symbolSet } from "../structures/type_constructors.js"; -import { mapRecursiveStructure } from './util.js'; import { getHumanReadableName } from '../primitives/symbol.js'; +import { getSymbol } from '../primitives/type.js'; export function pretty(obj) { return inspect(obj, { colors: true, depth: null, breakLength: 120 }); } // Pretty print Type -export const prettyT = type => { - return mapRecursiveStructure(([type, m, seen], map) => { - if (typeof type === "symbol") { - // type variable - return type.description; - } - // if (type.params === undefined) { - // throw new Error("parameter is not a Type ... did you mean to call prettyGenT instead?") - // } - if (!m.has(type)) { - m.add(type); // next time we encounter this type, we'll only render a placeholder - const params = type.params.map(p => map([p(type), m, seen])()); - // if while visiting the children, we encountered ourselves, add annotation: - const annot = seen.has(type) ? seen.get(type) : ``; - return renderType(type.symbol, annot, params); - } - if (!seen.has(type)) { - seen.set(type, `#${seen.size}`); - } - return seen.get(type); - })([type, new Set(), new Map()])(); -}; +export const _prettyT = (depth, tags) => type => { + if (typeof type === 'number' && type < depth) { + // we've already seen this type, so we'll tag it + // we mutate tags in-place so our parent type can see it + const hashTag = `#${tags.size}`; + // upper level will be tagged: + tags.set(type, hashTag); + // and this level is entirely replaced by tag: + return hashTag; + } + const params = type.params.map(p => _prettyT(depth+1, tags)(p(depth))); + const annot = tags.get(depth) || ''; + return renderType(getSymbol(type), annot, params); +} + +export const prettyT = type => _prettyT(0, new Map())(type); const renderType = (symbol, annot, params) => { return { @@ -42,8 +36,3 @@ const renderType = (symbol, annot, params) => { [symbolDict] : `${annot}(${params[0]} => ${params[1]})`, }[symbol] || getHumanReadableName(symbol); }; - -// Pretty print GenericType -export const prettyGenT = genericType => { - return `∀${[...genericType.typeVars].map(prettyT).sort((a, b) => a.localeCompare(b)).join(",")}: ${prettyT(genericType.type)}`; -}; diff --git a/lib/util/util.js b/lib/util/util.js index 4e401ca..48c4f01 100644 --- a/lib/util/util.js +++ b/lib/util/util.js @@ -25,3 +25,15 @@ const _mapRecursiveStructure = mapping => transform => root => { }; export const mapRecursiveStructure = _mapRecursiveStructure(new Map()); + +export const memoize = callback => { + let called = false + let result; + return () => { + if (!called) { + result = callback(); + called = true; + } + return result; + }; +}; diff --git a/lib/versioning/slot.js b/lib/versioning/slot.js index 49cd217..d5561b1 100644 --- a/lib/versioning/slot.js +++ b/lib/versioning/slot.js @@ -1,25 +1,31 @@ -import { add, emptySet, forEach } from "../../structures/set.js"; +import { add, emptySet, forEach } from "../structures/set.js"; import { deepEqual } from "../util/util.js"; import {inspect} from "node:util"; import { compareSlots } from "../compare/versioning.js"; -// UUID -> Value -> Slot -export const newSlot = uuid => value => ({ - overwrites: uuid, - value, - depth: 1, +export const newSlot = uuid => ({ + kind: "new", + uuid, + depth: 0, [inspect.custom]: (depth, options, inspect) => `newSlot(${inspect(uuid)}, ${inspect(value)})`, }); -// Slot -> Value -> Slot export const overwrite = slot => value => ({ + kind: "overwrite", overwrites: slot, value, depth: slot.depth + 1, // [inspect.custom]: (depth, options, inspect) => `overwrite(${inspect(slot)}, ${inspect(value)})`, }); +const slotsEqual = slotA => slotB => { + +} + const findLCA = slotA => slotB => { + if (slotA.depth === slotB.depth) { + + } if (deepEqual(slotA, slotB)) { return slotA; } @@ -31,7 +37,6 @@ const findLCA = slotA => slotB => { } }; -// Slot -> Slot -> MergeResult export const merge = compareElems => slotA => slotB => { const lca = findLCA(slotA)(slotB); if (lca === undefined) { @@ -49,7 +54,6 @@ export const merge = compareElems => slotA => slotB => { // return new Set([slotA, slotB]); }; -// MergeResult -> MergeResult -> MergeResult export const merge2 = compareElems => mA => mB => { let result = emptySet(compareSlots(compareElems)); forEach(mA)(slotOfA => { diff --git a/lib/versioning/types.js b/lib/versioning/types.js index ac55fb0..a4b0284 100644 --- a/lib/versioning/types.js +++ b/lib/versioning/types.js @@ -1,27 +1,47 @@ -import { Dynamic } from "../primitives/dynamic.js"; -import { Int } from "../primitives/types.js"; -import { enumType } from "../structures/enum.js"; +import { makeGeneric } from "../generics/generics.js"; +import { makeTypeConstructor } from "../meta/type_constructor.js"; +import { Int, UUID } from "../primitives/primitive_types.js"; +import { enumType, makeModuleEnum } from "../structures/enum.types.js"; import { newProduct } from "../structures/product.js"; -import { structType } from "../structures/struct.js"; -import { prodType } from "../structures/types.js"; +import { structType } from "../structures/struct.types.js"; import { prettyT } from "../util/pretty.js"; -const Slot = structType([ - newProduct("value")(() => Value), - newProduct("depth")(() => Int), - newProduct("overwrites")(() => Slot), +const Slot = makeTypeConstructor("Slot__318d1c1a9336c141336c461c6a3207b0")(1); +const Value = makeTypeConstructor("Value__23fc00a2db1374bd3dc1a0ad2d8517ab")(1); + +makeModuleEnum(Value)([ + ]); -// A Value is either: -// - a literal, without any dependencies. -// - read from a slot. the Value then has a read-dependency on that slot. -// - a transformation of another Value, by a function. the function is also a Value. -const variants = [ - newProduct("literal", () => Dynamic), - newProduct("read", () => Slot), - newProduct("transformation", () => prodType(Value, Dynamic)), -]; -const Value = enumType(variants); +const _slotType = a => Value => structType([ + newProduct("value")(_ => Value || _valueType(a)(Slot)), + newProduct("rest" )(_ => enumType([ + newProduct("new")(Slot => structType([ + newProduct("uuid" )(_ => UUID), + newProduct("value")(_ => Value || _valueType(a)(Slot)), + ])), + newProduct("overwrites")(Slot => structType([ + newProduct("slot" )(_ => Slot), + newProduct("value")(_ => Value || _valueType(a)(Slot)), + newProduct("depth")(_ => Int), + ])), + ])), +]); -// console.log(prettyT(Slot)); -// console.log(prettyT(Value)); \ No newline at end of file +const _valueType = a => Slot => enumType([ + newProduct("literal" )(_ => a), + newProduct("read" )(Value => Slot || _slotType(a)(Value)), + newProduct("transform")(Value => structType([ + newProduct("in" )(_ => Value), + newProduct("fn" )(_ => Value), + newProduct("out")(_ => a), + ])), +]); + +const slotType = makeGeneric(a => _slotType(a)(null)); +const valueType = makeGeneric(a => _valueType(a)(null)); + +console.log("slotType:", prettyT(slotType)); +console.log("valueType:", prettyT(valueType)); + +// const valueType = makeGeneric(a => _valueType(a)(_slotType)); \ No newline at end of file diff --git a/lib/versioning/value.js b/lib/versioning/value.js index 7e5cc4a..8974776 100644 --- a/lib/versioning/value.js +++ b/lib/versioning/value.js @@ -1,4 +1,3 @@ -import { fnType } from "../structures/types.js"; import { deepEqual } from "../util/util.js"; import { inspect } from "node:util"; @@ -20,7 +19,7 @@ export const read = slot => ({ // Value -> Value b> -> Value export const transform = input => fn => { const output = fn.out(input.out); - const _inspect = (depth, options, inspect) => `transform(${inspect(input)}, ${inspect(fn)})`; + // const _inspect = (depth, options, inspect) => `transform(${inspect(input)}, ${inspect(fn)})`; if (input.kind === "literal") { // optimization: sandwich everything together return { @@ -31,7 +30,7 @@ export const transform = input => fn => { } else { return { - kind: "transformation", + kind: "transform", in: input, fn, out: output, @@ -48,7 +47,7 @@ export const getReadDependencies = value => { else if (value.kind === "read") { return new Set([value.slot]); } - else if (value.kind === "transformation") { + else if (value.kind === "transform") { return new Set([ ...getReadDependencies(value.in), ...getReadDependencies(value.fn), @@ -80,9 +79,9 @@ export const verifyValue = (value, indent = 0) => { else if (value.kind === "read") { compare(value.out, value.slot.value.out, "read"); } - else if (value.kind === "transformation") { + else if (value.kind === "transform") { compare(value.fn.out(value.in.out), - value.out, "transformation"); + value.out, "transform"); success &= verifyValue(value.in, indent + 1); success &= verifyValue(value.fn, indent + 1); diff --git a/progress.txt b/progress.txt index 2e6eb9d..ebeb698 100644 --- a/progress.txt +++ b/progress.txt @@ -1,12 +1,20 @@ done: - - everything is properly typed, up to the meta-circular level - primitives - - structures: list, product, sum + - structures: list, product, sum, ... can compose structures, e.g., create list of list of product of sum of ... - list type is specialized for ListOfByte to use Uint8Array - - generic types and type inferencing + set and dictionary implemented as purely functional red-black tree + keys allowed: (anything with total ordering) + - primitive values + - structural values + - dynamically typed values + - types + - generic types and type unification (inferencing) + - recursive types todo: + - dynamically typed values: have 'Any' or 'Top' type? + + - to support sets of slots, need comparison of slots => comparison of values => problem: how to compare transformed values? their inputs can come from different types @@ -19,6 +27,5 @@ todo: (b) dirty: use 'magic' function that compares any JS value - - typeclass mechanism + - interfaces (type classes) - - what about type links: they connect anything to its type... what is the type of 'anything'? diff --git a/tests/generics.js b/tests/generics.js new file mode 100644 index 0000000..97fa238 --- /dev/null +++ b/tests/generics.js @@ -0,0 +1,67 @@ +import assert from "node:assert"; +import { assignFn, makeGeneric, unify, UnifyError } from "../lib/generics/generics.js"; +import { getDefaultTypeParser } from "../lib/parser/type_parser.js"; +import { prettyT } from "../lib/util/pretty.js"; + +const mkType = getDefaultTypeParser(); + +// It would be better to compare the types directly with 'compareTypes', but the assert-module does not allow passing a custom comparison function. + +assert.equal( + // actual + prettyT( + unify( + mkType("(a -> Int)"), + makeGeneric(() => mkType("[Bool] -> Int")), + ) + ), + // expected + "([Bool] -> Int)", +); + +assert.equal( + // actual + prettyT( + unify( + mkType("(a -> a) -> b"), + mkType("(Bool -> Bool) -> a"), + ) + ), + // expected + "((Bool -> Bool) -> a)", +); + +assert.equal( + // actual + prettyT( + assignFn( + mkType("(a -> b) -> [a] -> [b]"), + mkType("a -> a") + ) + ), + // expected + "([a] -> [a])", +); + +assert.equal( + // actual + prettyT( + assignFn( + mkType("(a -> Int) -> [a] -> a"), + mkType("a -> a") + ) + ), + // expected + "([Int] -> Int)", +); + +assert.throws( + () => { + unify( + mkType("Int"), + mkType("Bool") + ) + }, + // expected error + UnifyError, +); diff --git a/tests/parser.js b/tests/parser.js new file mode 100644 index 0000000..7bfb93d --- /dev/null +++ b/tests/parser.js @@ -0,0 +1,47 @@ +import assert from "node:assert"; +import { getDefaultTypeParser }from "../lib/parser/type_parser.js"; +import { prettyT } from "../lib/util/pretty.js"; + +const parse = getDefaultTypeParser(); + +assert.equal( + // actual + prettyT(parse("Int")), + // expected + "Int", +); + +assert.equal( + // actual + prettyT(parse("Int * Bool")), + // expected + "(Int ⨯ Bool)", +); + +assert.equal( + // actual + prettyT(parse("(((((((Int)))) => ((Bool)))))")), + // expected + "(Int => Bool)", +); + +assert.equal( + // actual + prettyT(parse("#0((Int * #0) + Unit)")), + // expected + "#0((Int ⨯ #0) + Unit)", +); + +assert.equal( + // actual + prettyT(parse("#0((a * #0) + Unit")), + // expected + "#0((a ⨯ #0) + Unit)", +); + +assert.equal( + // actual + prettyT(parse("(a*b) + (c*d)")), + // expected + "((a ⨯ b) + (c ⨯ d))", +);