simplify: no distinction between generic types and 'normal' types.

This commit is contained in:
Joeri Exelmans 2025-05-08 16:58:07 +02:00
parent b4826605af
commit a664ddac8a
27 changed files with 535 additions and 360 deletions

View file

@ -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 { Int, Unit } from "../lib/primitives/primitive_types.js";
import { unit } from "../lib/primitives/unit.js"; import { unit } from "../lib/primitives/unit.js";
import { makeConstructors, makeMatchFn } from "../lib/structures/enum.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 { lsType } from "../lib/structures/type_constructors.js";
import { prettyT } from "../lib/util/pretty.js"; import { prettyT } from "../lib/util/pretty.js";
Error.stackTraceLimit = Infinity;
const variants = [ const variants = [
newProduct("price")(Int), newProduct("price")(_ => Int),
newProduct("prices")(lsType(_ => Int)), newProduct("prices")(_ => lsType(_ => Int)),
newProduct("not_found")(Unit), newProduct("not_found")(_ => Unit),
]; ];
const myEnumType = enumType(variants); const compatibleNestedSumType = enumType(variants);
console.log("observe the type that was generated:"); console.log("observe the type that was generated:");
console.log(" ", prettyT(myEnumType)); console.log(" ", prettyT(compatibleNestedSumType));
const [newPrice, newPrices, newNotFound] = makeConstructors(variants); const [newPrice, newPrices, newNotFound] = makeConstructors(variants);
@ -39,7 +41,7 @@ console.log(" ", myEnumToString(price));
console.log(" ", myEnumToString(prices)); console.log(" ", myEnumToString(prices));
console.log(" ", myEnumToString(notFound)); console.log(" ", myEnumToString(notFound));
const compareMyEnum = makeCompareFn(myEnumType); const compareMyEnum = makeCompareFn(compatibleNestedSumType);
console.log("observe the generated compare function in action:"); console.log("observe the generated compare function in action:");
console.log(" should be smaller ->", compareMyEnum(price)(prices)); console.log(" should be smaller ->", compareMyEnum(price)(prices));

View file

@ -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")
)
));

View file

@ -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))

View file

@ -1,8 +1,9 @@
import { compareTypes } from "../lib/compare/type.js"; import { compareTypes } from "../lib/compare/type.js";
import { makeGeneric, substitute, unify } from "../lib/generics/generics.js"; import { makeGeneric, substitute, unify } from "../lib/generics/generics.js";
import { Double, Int, Unit } from "../lib/primitives/primitive_types.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 { 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; Error.stackTraceLimit = Infinity;
@ -28,8 +29,8 @@ const genericLinkedList = makeGeneric(a => makeLinkedList(a));
console.log(prettyT(listOfSetOfSelf)); // #0[{#0}] console.log(prettyT(listOfSetOfSelf)); // #0[{#0}]
console.log(prettyT(linkedListOfInt)); // #0((Int #0) + Unit) console.log(prettyT(linkedListOfInt)); // #0((Int #0) + Unit)
console.log(prettyGenT(genericFunction)); // ∀a,b: (a -> b) console.log(prettyT(genericFunction)); // (a -> b)
console.log(prettyGenT(genericLinkedList)); // ∀a: #0((a #0) + Unit) console.log(prettyT(genericLinkedList)); // #0((a #0) + Unit)
// comparison // comparison
@ -45,16 +46,15 @@ console.log(compareTypes(listOfSetOfSelf)(linkedListOfDouble)) // -1
const genericList = makeGeneric(a => lsType(_ => a)); const genericList = makeGeneric(a => lsType(_ => a));
const intList = lsType(_ => Int); const intList = lsType(_ => Int);
console.log(prettyGenT(genericList)); // ∀a: [a] console.log(prettyT(genericList)); // [a]
// substitution of type parameters // substitution of type parameters
const substituted = substitute( const substituted = substitute(
genericList.type, genericList,
new Map([[ new Map([
genericList.typeVars.keys().next().value, [TYPE_VARS[0], Int],
Int, ])
]])
); );
console.log(prettyT(substituted)); // [Int] console.log(prettyT(substituted)); // [Int]
@ -63,16 +63,15 @@ console.log(prettyT(substituted)); // [Int]
console.log("recursive substitution") console.log("recursive substitution")
console.log(prettyT(substitute( console.log(prettyT(substitute(
genericLinkedList.type, genericLinkedList,
new Map([[ new Map([
genericLinkedList.typeVars.keys().next().value, [TYPE_VARS[0], Int],
Int, ])
]])
))); // #0((Int #0) + Unit) ))); // #0((Int #0) + Unit)
// unification (simple case) // unification (simple case)
const {typeVars, type} = unify( const type = unify(
genericList, genericList,
makeGeneric(() => intList)); makeGeneric(() => intList));
@ -86,7 +85,7 @@ const unified = unify(
genericLinkedList, genericLinkedList,
makeGeneric(() => linkedListOfInt)); makeGeneric(() => linkedListOfInt));
console.log(prettyGenT(unified)); // ∀: #0((Int #0) + Unit) console.log(prettyT(unified)); // #0((Int #0) + Unit)
// unification (strange case) // unification (strange case)
@ -95,4 +94,4 @@ const unified2 = unify(
genericList, genericList,
); );
console.log(prettyGenT(unified2)); // ∀: #0[{#0}] console.log(prettyT(unified2)); // #0[{#0}]

View file

@ -1,8 +1,7 @@
import { pretty } from "../util/pretty.js"; import { pretty } from "../lib/util/pretty.js";
import { newLiteral, transform, read, getReadDependencies, verifyValue } from "../versioning/value.js"; import { newLiteral, transform, read, getReadDependencies, verifyValue } from "../lib/versioning/value.js";
import { merge, merge2, newSlot, overwrite } from "../versioning/slot.js"; import { merge, merge2, newSlot, overwrite } from "../lib/versioning/slot.js";
import { add, emptySet, RBTreeWrapper } from "../structures/set.js"; import { compareNumbers } from "../lib/compare/primitives.js";
import { compareNumbers } from "../compare/primitives.js";
const inc = x => x + 1; const inc = x => x + 1;

View file

@ -1,22 +1,32 @@
import { getInst, getType } from "../primitives/dynamic.js"; import { getInst, getType } from "../primitives/dynamic.js";
import { SymbolBool, SymbolChar, SymbolDouble, SymbolInt, SymbolType, SymbolUnit } from "../primitives/primitive_types.js"; import { SymbolBool, SymbolBottom, SymbolByte, SymbolChar, SymbolDouble, SymbolDynamic, SymbolInt, SymbolUUID, SymbolType, SymbolUnit } from "../primitives/primitive_types.js";
import { symbolDict, symbolList, symbolProduct, symbolSet, symbolSum } from "../structures/type_constructors.js"; import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from "../structures/type_constructors.js";
import { compareBools, compareNumbers, compareStrings, compareUnits } from "./primitives.js"; import { compareBools, compareNumbers, compareStrings, compareSymbols, compareUnits } from "./primitives.js";
import { compareDicts, compareLists, compareProducts, compareSets, compareSums } from "./structures.js"; import { compareDicts, compareFunctions, compareLists, compareProducts, compareSets, compareSums } from "./structures.js";
import { compareTypes } from "./type.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([ const typeSymbolToCmp = new Map([
[SymbolInt , compareNumbers], [SymbolInt , compareNumbers],
[SymbolChar , compareStrings],
[SymbolDouble , compareNumbers],
[SymbolBool , compareBools], [SymbolBool , compareBools],
[SymbolDouble , compareNumbers],
[SymbolByte , compareNumbers],
[SymbolChar , compareStrings],
[SymbolUnit , compareUnits], [SymbolUnit , compareUnits],
[SymbolBottom , _ => _ => { throw new Error("Bottom!"); }],
[SymbolUUID , compareSymbols],
// [SymbolGenericType, ?] TODO
[SymbolType , compareTypes], [SymbolType , compareTypes],
[SymbolDynamic, compareDynamic],
// these functions take extra comparison callbacks: // these functions take extra comparison callbacks:
[symbolList , compareLists], [symbolFunction, compareFunctions],
[symbolProduct , compareProducts],
[symbolSum , compareSums], [symbolSum , compareSums],
[symbolProduct , compareProducts],
[symbolList , compareLists],
[symbolSet , compareSets], [symbolSet , compareSets],
[symbolDict , compareDicts], [symbolDict , compareDicts],
]); ]);
@ -27,7 +37,3 @@ export const makeCompareFn = type => {
typeSymbolToCmp.get(type.symbol) typeSymbolToCmp.get(type.symbol)
); );
}; };
export const compareDynamic = x => y =>
compareTypes(getType(x))(getType(y))
|| makeCompareFn(getType(x))(getInst(x))(getInst(y));

View file

@ -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")},
];

View file

@ -1,12 +1,17 @@
// Total ordering of composed types // 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 { 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 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 { read as readDict, length as lengthDict, first as firstDict } from "../structures/dict.js";
import { getLeft, getRight } from "../structures/product.js"; import { getLeft, getRight } from "../structures/product.js";
import { match } from "../structures/sum.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 => { export const compareLists = compareElems => x => y => {
return compareNumbers(lengthLs(x))(lengthLs(y)) return compareNumbers(lengthLs(x))(lengthLs(y))
|| (() => { || (() => {

View file

@ -1,64 +1,57 @@
import { eqType } from "../primitives/type.js"; import { eqType, getSymbol } from "../primitives/type.js";
import { zip } from "../util/util.js"; import { zip } from "../util/util.js";
import { pretty, prettyT } from '../util/pretty.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: // for instance, the type:
// ∀a: a -> a -> Bool // ∀a: a -> a -> Bool
// is created by // is created by
// makeGeneric(a => fnType(() => a)(() => fnType(() => a)(() => Bool))) // makeGeneric(a => fnType(() => a)(() => fnType(() => a)(() => Bool)))
export const makeGeneric = callback => { export const makeGeneric = callback => {
// type variables to make available: // type variables to make available:
const typeVars = ['a', 'b', 'c', 'd', 'e'].map(Symbol); const type = callback(...TYPE_VARS);
const type = callback(...typeVars); return type;
return onlyOccurring(type, new Set(typeVars));
}; };
export const onlyOccurring = (type, typeVars) => ({ const _occurring = stack => type => {
typeVars: occurring(type, typeVars), if (isTypeVar(type)) {
type, return new Set([getSymbol(type)]);
});
const __occurring = state => typeVars => type => {
if (typeVars.has(type)) {
return new Set([type]);
} }
const tag = state.nextTag++; const tag = stack.length;
state.seen.add(tag); const newStack = [...stack, tag];
return new Set(type.params.flatMap(p => { return new Set(type.params.flatMap(p => {
const innerType = p(tag); const innerType = p(tag);
if (state.seen.has(innerType)) { if (newStack.includes(innerType)) {
return []; // no endless recursion! 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. // Get set of type variables in type.
export const occurring = (type, typeVars) => { export const occurring = _occurring([]);
return __occurring({nextTag:0, seen: new Set()})(typeVars)(type);
};
// Merge 2 substitution-mappings, uni-directional. // Merge 2 substitution-mappings, uni-directional.
const mergeOneWay = (m1, m2) => { const mergeOneWay = (m1, m2) => {
const m1copy = new Map(m1); const m1copy = new Map(m1);
const m2copy = new Map(m2); const m2copy = new Map(m2);
for (const [var1, typ1] of m1copy.entries()) { for (const [symbol1, typ1] of m1copy) {
if (m2copy.has(typ1)) { if (m2copy.has(getSymbol(typ1))) {
// typ1 is a typeVar for which we also have a substitution // typ1 is a typeVar for which we also have a substitution
// -> fold substitutions // -> fold substitutions
m1copy.set(var1, m2.get(typ1)); m1copy.set(symbol1, m2.get(getSymbol(typ1)));
m2copy.delete(typ1); m2copy.delete(getSymbol(typ1));
return [false, m1copy, m2copy, new Set([typ1])]; return [false, m1copy, m2copy];
} }
} }
return [true, m1copy, m2copy, new Set()]; // stable return [true, m1copy, m2copy]; // stable
}; };
const checkConflict = (m1, m2) => { const checkConflict = (m1, m2) => {
for (const [var1, typ1] of m1) { for (const [symbol1, typ1] of m1) {
if (m2.has(var1)) { if (m2.has(symbol1)) {
const other = m2.get(var1); const other = m2.get(symbol1);
if (!eqType(typ1, other)) { if (!eqType(typ1, other)) {
throw new Error(`conflicting substitution: ${pretty(typ1)}vs. ${pretty(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... // checkConflict(m2, m1); // <- don't think this is necessary...
// actually merge // actually merge
let stable = false; let stable = false;
let deletions = new Set();
while (!stable) { while (!stable) {
let d;
// notice we swap m2 and m1, so the rewriting can happen both ways: // notice we swap m2 and m1, so the rewriting can happen both ways:
[stable, m2, m1, d] = mergeOneWay(m1, m2); [stable, m2, m1] = mergeOneWay(m1, m2);
deletions = deletions.union(d);
} }
const result = { const result = new Map([...m1, ...m2]);
substitutions: new Map([...m1, ...m2]),
deletions, // deleted type variables
};
// console.log("mergeTwoWay result =", result); // console.log("mergeTwoWay result =", result);
return 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): // 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 // 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 // typeVars: all the type variables in both fType and aType
// fType, aType: generic types to unify // fType, aType: generic types to unify
// fStack, aStack: internal use. // 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}); // 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 // simplest case: formalType is a type paramater
// => substitute with actualType // => substitute with actualType
// console.log(`assign ${prettyT(aType)} to ${prettyT(fType)}`); // console.log(`assign ${prettyT(aType)} to ${prettyT(fType)}`);
return { return {
substitutions: new Map([[fType, aType]]), substitutions: new Map([[getSymbol(fType), aType]]),
genericType: {
typeVars: typeVars.difference(new Set([fType])),
type: aType, type: aType,
},
}; };
} }
if (typeVars.has(aType)) { if (isTypeVar(aType)) {
// same as above, but in the other direction // same as above, but in the other direction
// console.log(`assign ${prettyT(fType)} to ${prettyT(aType)}`); // console.log(`assign ${prettyT(fType)} to ${prettyT(aType)}`);
return { return {
substitutions: new Map([[aType, fType]]), substitutions: new Map([[getSymbol(aType), fType]]),
genericType: {
typeVars: typeVars.difference(new Set([aType])),
type: fType, type: fType,
},
}; };
} }
// recursively unify // recursively unify
if (fType.symbol !== aType.symbol) { 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; 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 // type recursively points to an enclosing type that we've already seen
if (fStack[fParam] !== aStack[aParam]) { if (fStack[fParam] !== aStack[aParam]) {
// note that both are also allowed not to be mapped (undefined) // 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) { if (fStack[fParam] !== undefined) {
const type = fStack[fParam]; const type = fStack[fParam];
return () => ({ return () => ({
substitutions: new Map(), substitutions: new Map(),
genericType: {
typeVars,
type, type,
},
}); });
} }
return parent => __unify(typeVars, fParam, aParam, return parent => __unify(fParam, aParam,
[...fStack, parent], [...fStack, parent],
[...aStack, parent]); [...aStack, parent]);
}); });
const unifiedParams = unifications.map(getParam => { 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 self = Symbol(); // dirty, just need something unique
const {substitutions, deletions} = mergeTwoWay(acc[0], getParam(self).substitutions); const paramSubstitutions = getParam(self).substitutions;
return [substitutions, acc[1] const substitutions = mergeTwoWay(acc, paramSubstitutions);
.difference(substitutions) return substitutions;
.difference(deletions)]; }, new Map());
}, [new Map(), typeVars]);
return { return {
substitutions: unifiedSubstitutions, substitutions: unifiedSubstitutions,
genericType: { type: {
typeVars: unifiedTypeVars, symbol: fType.symbol,
type, params: unifiedParams,
}, },
}; };
}; };
export const unify = (fGenericType, aGenericType) => { export const unify = (fType, aType) => {
let allTypeVars; [fType, aType] = recomputeTypeVars([fType, aType]);
[allTypeVars, fGenericType, aGenericType] = safeUnionTypeVars(fGenericType, aGenericType); const {type, substitutions} = __unify(fType, aType);
const {genericType, substitutions} = __unify(allTypeVars, fGenericType.type, aGenericType.type);
// console.log('unification complete! substitutions:', substitutions); // console.log('unification complete! substitutions:', substitutions);
return recomputeTypeVars(genericType); return recomputeTypeVars([type])[0];
}; };
export const substitute = (type, substitutions, stack=[]) => { export const substitute = (type, substitutions, stack=[]) => {
// console.log('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 => { params: type.params.map(getParam => parent => {
const param = getParam(stack.length); const param = getParam(stack.length);
const have = stack[param]; return stack[param]
return (have !== undefined) || substitute(param, substitutions, [...stack, parent]);
? have
: substitute(param, substitutions, [...stack, parent]);
}), }),
}; };
}; };
export const assign = (genFnType, paramType) => { export const assignFn = (funType, paramType) => {
let allTypeVars; [funType, paramType] = recomputeTypeVars([funType, paramType]);
[allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType); // console.log(prettyT(funType), prettyT(paramType));
const [inType, outType] = genFnType.type.params; const [inType, outType] = funType.params.map(p => p(funType));
const {substitutions} = __unify(allTypeVars, inType(genFnType.type), paramType.type); const {substitutions} = __unify(inType, paramType);
const substitutedOutType = substitute(outType(genFnType.type), substitutions); // console.log(substitutions, prettyT(outType));
return recomputeTypeVars(onlyOccurring(substitutedOutType, allTypeVars)); const substitutedFnType = substitute(outType, substitutions);
return recomputeTypeVars([substitutedFnType])[0];
}; };
export const assignFn = (genFnType, paramType) => { // Ensures that no type variables overlap
let allTypeVars; export const recomputeTypeVars = types => {
[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);
let nextIdx = 0; let nextIdx = 0;
const subst = new Map(); return types.map(type => {
for (const typeVarA of genType.typeVars) { const substitutions = new Map();
subst.set(typeVarA, newTypeVars[nextIdx++]); const typeVars = occurring(type);
for (const typeVar of typeVars) {
substitutions.set(typeVar, TYPE_VARS[nextIdx++]);
} }
const substType = { return substitute(type, substitutions);
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];
};

View file

@ -1,7 +1,10 @@
// A simple, hacked-together recursive parser for types. // 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 { 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 { dictType, fnType, lsType, prodType, sumType } from "../structures/type_constructors.js";
import { setType } from "../structures/type_constructors.js"; import { setType } from "../structures/type_constructors.js";
@ -11,12 +14,6 @@ export const makeTypeParser = ({
extraBracketOperators=[], extraBracketOperators=[],
extraInfixOperators=[], 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([ const primitives = new Map([
['Int', Int], ['Int', Int],
['Double', Double], ['Double', Double],
@ -27,12 +24,9 @@ export const makeTypeParser = ({
['Unit', Unit], ['Unit', Unit],
['Type', Type], ['Type', Type],
['Dynamic', Dynamic], ['Dynamic', Dynamic],
['SymbolT', SymbolT], ['UUID', UUID],
['a', a],
['b', b], ...TYPE_VARS.map(type => [getHumanReadableName(getSymbol(type)), type]),
['c', c],
['d', d],
['e', e],
...extraPrimitives, ...extraPrimitives,
]); ]);
@ -42,10 +36,6 @@ export const makeTypeParser = ({
['[', [']', lsType]], ['[', [']', lsType]],
['{', ['}', setType]], ['{', ['}', setType]],
// can only occur at beginning
// we use these to extract the type variables
['∀', [':', null]],
...extraBracketOperators, ...extraBracketOperators,
]); ]);
@ -187,13 +177,6 @@ export const makeTypeParser = ({
const parse = expr => { const parse = expr => {
const tokens = tokenize(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); return __parse(tokens);
} }

View file

@ -9,10 +9,9 @@ export const SymbolByte = "Byte__bf9e8453cd554e81971880ba33dc9f27";
export const SymbolChar = "Char__e47159519d3345119336b751fc8da1de"; export const SymbolChar = "Char__e47159519d3345119336b751fc8da1de";
export const SymbolUnit = "Unit__a70ca021c32a4036a594d332aedfb029"; export const SymbolUnit = "Unit__a70ca021c32a4036a594d332aedfb029";
export const SymbolBottom = "⊥__95beece951bc457781be8c5481d35dcc"; export const SymbolBottom = "⊥__95beece951bc457781be8c5481d35dcc";
export const SymbolSymbol = "Symbol__f67c077430e04e4fa40ed2e2b2a3040d"; export const SymbolUUID = "UUID__f67c077430e04e4fa40ed2e2b2a3040d";
export const SymbolType = "Type__fdbea309d66f49b483b0dd4ceb785f7d"; export const SymbolType = "Type__fdbea309d66f49b483b0dd4ceb785f7d";
export const SymbolTop = "__38709c3c0039468782103256d4730d1f"; export const SymbolTop = "__38709c3c0039468782103256d4730d1f";
export const SymbolGenericType = "GenericType__e9d8010b82f64206afa83d0c163df5d2";
export const SymbolDynamic = "Dynamic__3c16c415dba94228ada37dc9d446f54f"; export const SymbolDynamic = "Dynamic__3c16c415dba94228ada37dc9d446f54f";
export const Int = makeTypeConstructor(SymbolInt)(0); export const Int = makeTypeConstructor(SymbolInt)(0);
@ -27,13 +26,11 @@ export const Unit = makeTypeConstructor(SymbolUnit)(0);
// Bottom type has no instances. // Bottom type has no instances.
export const Bottom = makeTypeConstructor(SymbolBottom)(0); export const Bottom = makeTypeConstructor(SymbolBottom)(0);
export const SymbolT = makeTypeConstructor(SymbolSymbol)(0); export const UUID = makeTypeConstructor(SymbolUUID)(0);
// Types are typed by Top // Types are typed by Top
export const Type = makeTypeConstructor(SymbolType)(0); export const Type = makeTypeConstructor(SymbolType)(0);
export const GenericType = makeTypeConstructor(SymbolGenericType)(0);
// Everything is typed by Top // Everything is typed by Top
export const Top = makeTypeConstructor(SymbolTop)(0);// A type-link, connecting a value to its Type. export const Top = makeTypeConstructor(SymbolTop)(0);// A type-link, connecting a value to its Type.

View file

@ -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 = [ export const ModulePrimitiveSymbols = [
{ i: SymbolInt , t: SymbolT }, { i: SymbolInt , t: UUID },
{ i: SymbolBool , t: SymbolT }, { i: SymbolBool , t: UUID },
{ i: SymbolDouble , t: SymbolT }, { i: SymbolDouble , t: UUID },
{ i: SymbolByte , t: SymbolT }, { i: SymbolByte , t: UUID },
{ i: SymbolChar , t: SymbolT }, { i: SymbolChar , t: UUID },
{ i: SymbolUnit , t: SymbolT }, { i: SymbolUnit , t: UUID },
{ i: SymbolBottom , t: SymbolT }, { i: SymbolBottom , t: UUID },
{ i: SymbolSymbol , t: SymbolT }, { i: SymbolUUID , t: UUID },
{ i: SymbolType , t: SymbolT }, { i: SymbolType , t: UUID },
{ i: SymbolGenericType , t: SymbolT }, { i: SymbolTop , t: UUID },
{ i: SymbolTop , t: SymbolT }, { i: SymbolDynamic , t: UUID },
{ i: SymbolDynamic , t: SymbolT },
]; ];
export const ModulePrimitiveTypes = [ export const ModulePrimitiveTypes = [
@ -23,9 +22,8 @@ export const ModulePrimitiveTypes = [
{ i: Char , t: Type }, { i: Char , t: Type },
{ i: Unit , t: Type }, { i: Unit , t: Type },
{ i: Bottom , t: Type }, { i: Bottom , t: Type },
{ i: SymbolT , t: Type }, { i: UUID , t: Type },
{ i: Type , t: Type }, { i: Type , t: Type },
{ i: GenericType , t: Type },
{ i: Top , t: Type }, { i: Top , t: Type },
{ i: Dynamic , t: Type }, { i: Dynamic , t: Type },
]; ];

View file

@ -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));
};

View file

@ -11,7 +11,7 @@ const eatParameters = (numParams, result) => {
export const makeMatchFn = variants => { export const makeMatchFn = variants => {
if (variants.length === 0) { if (variants.length === 0) {
return undefined; throw new Error("Bottom!");
} }
const [_, ...remainingVariants] = variants; const [_, ...remainingVariants] = variants;
return sum => handler => { return sum => handler => {
@ -34,4 +34,4 @@ export const makeConstructors = variants => {
...makeConstructors(remainingVariants).map(ctor => ...makeConstructors(remainingVariants).map(ctor =>
({[ctor.name]: val => newRight(ctor(val))}[ctor.name])), ({[ctor.name]: val => newRight(ctor(val))}[ctor.name])),
]; ];
} };

View file

@ -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 { 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. // 'variants' is an array of (name: string, type: Type) pairs.
// e.g., the list of variants: // e.g., the list of variants:
@ -10,11 +14,67 @@ import { sumType } from "./type_constructors.js";
// results in the type: // results in the type:
// (Int | ([Int] | (Unit | ⊥))) // (Int | ([Int] | (Unit | ⊥)))
export const enumType = variants => { const _enumType = rootSelf => variants => {
// console.log("enumType..", variants);
if (variants.length === 0) { if (variants.length === 0) {
return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated) return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated)
} }
const [variant, ...rest] = variants; const [variant, ...rest] = variants;
const variantType = getRight(variant); 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)))
];
}

View file

@ -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 remove = set => key => new RBTreeWrapper(set.tree.remove(key));
export const length = set => set.tree.length; 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 first = set => set.tree.begin;
export const last = set => set.tree.end; export const last = set => set.tree.end;

View file

@ -1,6 +1,6 @@
import { makeTypeParser } from "../parser/type_parser.js"; import { makeTypeParser } from "../parser/type_parser.js";
import { makeTypeConstructor } from "../meta/type_constructor.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); const setIterator = makeTypeConstructor('SetIterator__f6b0ddd78ed41c58e5a442f2681da011')(1);
@ -14,6 +14,7 @@ export const ModuleSet = [
{ i: add , t: mkType("∀a: {a} -> a -> {a}")}, { i: add , t: mkType("∀a: {a} -> a -> {a}")},
{ i: remove , t: mkType("∀a: {a} -> a -> {a}")}, { i: remove , t: mkType("∀a: {a} -> a -> {a}")},
{ i: length , t: mkType("∀a: {a} -> Int")}, { i: length , t: mkType("∀a: {a} -> Int")},
{ i: fold , t: mkType("∀a,b: {a} -> (b -> a -> b) -> b")},
{ i: first , t: mkType("∀a: {a} -> <a>")}, { i: first , t: mkType("∀a: {a} -> <a>")},
{ i: last , t: mkType("∀a: {a} -> <a>")}, { i: last , t: mkType("∀a: {a} -> <a>")},
{ i: read , t: mkType("∀a: <a> -> (Unit + (a * <a>))")}, { i: read , t: mkType("∀a: <a> -> (Unit + (a * <a>))")},

View file

@ -13,40 +13,56 @@ import { fnType, prodType } from "./type_constructors.js";
// [{l: "x", r: Double}, {l: "y", r: Double}] // [{l: "x", r: Double}, {l: "y", r: Double}]
// results in the type (Double × (Double × Unit)) // results in the type (Double × (Double × Unit))
export const structType = fieldTypes => { export const structType = (fields, rootSelf) => {
if (fieldTypes.length === 0) { // console.log("structType..", fields);
if (fields.length === 0) {
return Unit; return Unit;
} }
const [fieldType, ...rest] = fieldTypes; const [field, ...rest] = fields;
return prodType(_ => fieldType)(_ => structType(rest)); 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 => { export const makeConstructorType = type => fields => {
if (fieldTypes.length === 0) { if (fields.length === 0) {
return structType(fieldTypes); return type;
// return structType(fields);
} }
const [fieldType, ...rest] = fieldTypes; const [field, ...rest] = fields;
return fnType(_ => fieldType)(_ => makeConstructorType(rest)); const fieldType = getRight(field);
return fnType(_ => fieldType)(_ => makeConstructorType(type, rest));
}; };
export const makeGettersTypes = fieldTypes => { export const makeGettersTypes = type => fields => {
const type = structType(fieldTypes); // const type = structType(fields);
return fieldTypes.map(fieldType => { return fields.map(field => {
const fieldType = getRight(field);
return fnType(_ => type)(_ => fieldType); return fnType(_ => type)(_ => fieldType);
}); });
}; };
export const makeModuleStruct = fields => { export const makeModuleStruct = type => fields => {
const fieldNames = map(fields)(getLeft); const fieldNames = map(fields)(getLeft);
const fieldTypes = map(fields)(getRight); // const type = structType(fields);
const type = structType(fieldTypes);
const ctor = makeConstructor(fields.length); const ctor = makeConstructor(fields.length);
const ctorType = makeConstructorType(fieldTypes); const ctorType = makeConstructorType(fields);
const getterTypes = makeGettersTypes(fieldTypes); const getterTypes = makeGettersTypes(fields);
const getters = makeGetters(fieldNames); const getters = makeGetters(fieldNames);
const module = [ const module = [
{i: type, t: Type}, // {i: type, t: Type},
// constructor
{i: ctor, t: ctorType}, {i: ctor, t: ctorType},
// getters:
...zip(getters, getterTypes) ...zip(getters, getterTypes)
.map(([getter, getterType]) => newDynamic(getter)(getterType)), .map(([getter, getterType]) => newDynamic(getter)(getterType)),
]; ];

View file

@ -1,16 +1,16 @@
import { getDefaultTypeParser } from "../parser/type_parser.js"; 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"; import { dictType, fnType, lsType, prodType, setType, sumType, symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from "./type_constructors.js";
const mkType = getDefaultTypeParser(); const mkType = getDefaultTypeParser();
export const ModuleStructuralSymbols = [ export const ModuleStructuralSymbols = [
{ i: symbolSet , t: SymbolT }, { i: symbolSet , t: UUID },
{ i: symbolList , t: SymbolT }, { i: symbolList , t: UUID },
{ i: symbolProduct , t: SymbolT }, { i: symbolProduct , t: UUID },
{ i: symbolSum , t: SymbolT }, { i: symbolSum , t: UUID },
{ i: symbolDict , t: SymbolT }, { i: symbolDict , t: UUID },
{ i: symbolFunction , t: SymbolT }, { i: symbolFunction , t: UUID },
]; ];
export const ModuleTypeConstructors = [ export const ModuleTypeConstructors = [

View file

@ -1,36 +1,30 @@
import { inspect } from 'node:util'; import { inspect } from 'node:util';
import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSum } from '../structures/type_constructors.js'; import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSum } from '../structures/type_constructors.js';
import { symbolSet } from "../structures/type_constructors.js"; import { symbolSet } from "../structures/type_constructors.js";
import { mapRecursiveStructure } from './util.js';
import { getHumanReadableName } from '../primitives/symbol.js'; import { getHumanReadableName } from '../primitives/symbol.js';
import { getSymbol } from '../primitives/type.js';
export function pretty(obj) { export function pretty(obj) {
return inspect(obj, { colors: true, depth: null, breakLength: 120 }); return inspect(obj, { colors: true, depth: null, breakLength: 120 });
} }
// Pretty print Type // Pretty print Type
export const prettyT = type => { export const _prettyT = (depth, tags) => type => {
return mapRecursiveStructure(([type, m, seen], map) => { if (typeof type === 'number' && type < depth) {
if (typeof type === "symbol") { // we've already seen this type, so we'll tag it
// type variable // we mutate tags in-place so our parent type can see it
return type.description; const hashTag = `#${tags.size}`;
// upper level will be tagged:
tags.set(type, hashTag);
// and this level is entirely replaced by tag:
return hashTag;
} }
// if (type.params === undefined) { const params = type.params.map(p => _prettyT(depth+1, tags)(p(depth)));
// throw new Error("parameter is not a Type ... did you mean to call prettyGenT instead?") const annot = tags.get(depth) || '';
// } return renderType(getSymbol(type), annot, params);
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])()); export const prettyT = type => _prettyT(0, new Map())(type);
// 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()])();
};
const renderType = (symbol, annot, params) => { const renderType = (symbol, annot, params) => {
return { return {
@ -42,8 +36,3 @@ const renderType = (symbol, annot, params) => {
[symbolDict] : `${annot}(${params[0]} => ${params[1]})`, [symbolDict] : `${annot}(${params[0]} => ${params[1]})`,
}[symbol] || getHumanReadableName(symbol); }[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)}`;
};

View file

@ -25,3 +25,15 @@ const _mapRecursiveStructure = mapping => transform => root => {
}; };
export const mapRecursiveStructure = _mapRecursiveStructure(new Map()); export const mapRecursiveStructure = _mapRecursiveStructure(new Map());
export const memoize = callback => {
let called = false
let result;
return () => {
if (!called) {
result = callback();
called = true;
}
return result;
};
};

View file

@ -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 { deepEqual } from "../util/util.js";
import {inspect} from "node:util"; import {inspect} from "node:util";
import { compareSlots } from "../compare/versioning.js"; import { compareSlots } from "../compare/versioning.js";
// UUID -> Value<a> -> Slot<a> export const newSlot = uuid => ({
export const newSlot = uuid => value => ({ kind: "new",
overwrites: uuid, uuid,
value, depth: 0,
depth: 1,
[inspect.custom]: (depth, options, inspect) => `newSlot(${inspect(uuid)}, ${inspect(value)})`, [inspect.custom]: (depth, options, inspect) => `newSlot(${inspect(uuid)}, ${inspect(value)})`,
}); });
// Slot<a> -> Value<a> -> Slot<a>
export const overwrite = slot => value => ({ export const overwrite = slot => value => ({
kind: "overwrite",
overwrites: slot, overwrites: slot,
value, value,
depth: slot.depth + 1, depth: slot.depth + 1,
// [inspect.custom]: (depth, options, inspect) => `overwrite(${inspect(slot)}, ${inspect(value)})`, // [inspect.custom]: (depth, options, inspect) => `overwrite(${inspect(slot)}, ${inspect(value)})`,
}); });
const slotsEqual = slotA => slotB => {
}
const findLCA = slotA => slotB => { const findLCA = slotA => slotB => {
if (slotA.depth === slotB.depth) {
}
if (deepEqual(slotA, slotB)) { if (deepEqual(slotA, slotB)) {
return slotA; return slotA;
} }
@ -31,7 +37,6 @@ const findLCA = slotA => slotB => {
} }
}; };
// Slot<a> -> Slot<a> -> MergeResult<a>
export const merge = compareElems => slotA => slotB => { export const merge = compareElems => slotA => slotB => {
const lca = findLCA(slotA)(slotB); const lca = findLCA(slotA)(slotB);
if (lca === undefined) { if (lca === undefined) {
@ -49,7 +54,6 @@ export const merge = compareElems => slotA => slotB => {
// return new Set([slotA, slotB]); // return new Set([slotA, slotB]);
}; };
// MergeResult<a> -> MergeResult<a> -> MergeResult<a>
export const merge2 = compareElems => mA => mB => { export const merge2 = compareElems => mA => mB => {
let result = emptySet(compareSlots(compareElems)); let result = emptySet(compareSlots(compareElems));
forEach(mA)(slotOfA => { forEach(mA)(slotOfA => {

View file

@ -1,27 +1,47 @@
import { Dynamic } from "../primitives/dynamic.js"; import { makeGeneric } from "../generics/generics.js";
import { Int } from "../primitives/types.js"; import { makeTypeConstructor } from "../meta/type_constructor.js";
import { enumType } from "../structures/enum.js"; import { Int, UUID } from "../primitives/primitive_types.js";
import { enumType, makeModuleEnum } from "../structures/enum.types.js";
import { newProduct } from "../structures/product.js"; import { newProduct } from "../structures/product.js";
import { structType } from "../structures/struct.js"; import { structType } from "../structures/struct.types.js";
import { prodType } from "../structures/types.js";
import { prettyT } from "../util/pretty.js"; import { prettyT } from "../util/pretty.js";
const Slot = structType([ const Slot = makeTypeConstructor("Slot__318d1c1a9336c141336c461c6a3207b0")(1);
newProduct("value")(() => Value), const Value = makeTypeConstructor("Value__23fc00a2db1374bd3dc1a0ad2d8517ab")(1);
newProduct("depth")(() => Int),
newProduct("overwrites")(() => Slot), makeModuleEnum(Value)([
]); ]);
// A Value is either: const _slotType = a => Value => structType([
// - a literal, without any dependencies. newProduct("value")(_ => Value || _valueType(a)(Slot)),
// - read from a slot. the Value then has a read-dependency on that slot. newProduct("rest" )(_ => enumType([
// - a transformation of another Value, by a function. the function is also a Value. newProduct("new")(Slot => structType([
const variants = [ newProduct("uuid" )(_ => UUID),
newProduct("literal", () => Dynamic), newProduct("value")(_ => Value || _valueType(a)(Slot)),
newProduct("read", () => Slot), ])),
newProduct("transformation", () => prodType(Value, Dynamic)), newProduct("overwrites")(Slot => structType([
]; newProduct("slot" )(_ => Slot),
const Value = enumType(variants); newProduct("value")(_ => Value || _valueType(a)(Slot)),
newProduct("depth")(_ => Int),
])),
])),
]);
// console.log(prettyT(Slot)); const _valueType = a => Slot => enumType([
// console.log(prettyT(Value)); 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));

View file

@ -1,4 +1,3 @@
import { fnType } from "../structures/types.js";
import { deepEqual } from "../util/util.js"; import { deepEqual } from "../util/util.js";
import { inspect } from "node:util"; import { inspect } from "node:util";
@ -20,7 +19,7 @@ export const read = slot => ({
// Value<a> -> Value<a -> b> -> Value<b> // Value<a> -> Value<a -> b> -> Value<b>
export const transform = input => fn => { export const transform = input => fn => {
const output = fn.out(input.out); 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") { if (input.kind === "literal") {
// optimization: sandwich everything together // optimization: sandwich everything together
return { return {
@ -31,7 +30,7 @@ export const transform = input => fn => {
} }
else { else {
return { return {
kind: "transformation", kind: "transform",
in: input, in: input,
fn, fn,
out: output, out: output,
@ -48,7 +47,7 @@ export const getReadDependencies = value => {
else if (value.kind === "read") { else if (value.kind === "read") {
return new Set([value.slot]); return new Set([value.slot]);
} }
else if (value.kind === "transformation") { else if (value.kind === "transform") {
return new Set([ return new Set([
...getReadDependencies(value.in), ...getReadDependencies(value.in),
...getReadDependencies(value.fn), ...getReadDependencies(value.fn),
@ -80,9 +79,9 @@ export const verifyValue = (value, indent = 0) => {
else if (value.kind === "read") { else if (value.kind === "read") {
compare(value.out, value.slot.value.out, "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), compare(value.fn.out(value.in.out),
value.out, "transformation"); value.out, "transform");
success &= verifyValue(value.in, indent + 1); success &= verifyValue(value.in, indent + 1);
success &= verifyValue(value.fn, indent + 1); success &= verifyValue(value.fn, indent + 1);

View file

@ -1,12 +1,20 @@
done: done:
- everything is properly typed, up to the meta-circular level
- primitives - primitives
- structures: list, product, sum - structures: list, product, sum, ...
can compose structures, e.g., create list of list of product of sum of ... can compose structures, e.g., create list of list of product of sum of ...
list type is specialized for ListOfByte to use Uint8Array set and dictionary implemented as purely functional red-black tree
- generic types and type inferencing keys allowed: (anything with total ordering)
- primitive values
- structural values
- dynamically typed values
- types
- generic types and type unification (inferencing)
- recursive types
todo: todo:
- dynamically typed values: have 'Any' or 'Top' type?
- to support sets of slots, need comparison of slots - to support sets of slots, need comparison of slots
=> comparison of values => comparison of values
=> problem: how to compare transformed values? their inputs can come from different types => 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 (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'?

67
tests/generics.js Normal file
View file

@ -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,
);

47
tests/parser.js Normal file
View file

@ -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))",
);