greatly simplify type registry

This commit is contained in:
Joeri Exelmans 2025-03-20 17:29:13 +01:00
parent 303fa869a8
commit 4ca60784aa
3 changed files with 36 additions and 85 deletions

View file

@ -1,94 +1,47 @@
// This module ensures that we never accidentally create a Symbol for the same type twice.
// Maybe we shouldn't use Symbols for types, but we also cannot use primitive values for types because they may accidentally overlap with 'real' values (that are not types)
// We also cannot use objects for types because similar to Symbols, objects are only equal to themselves if we use them as Map keys.
// This module ensures that we never accidentally create more than one JS object for the same type twice.
// We do so by creating (nested) DefaultMap(s) for all non-nullary type constructors
// It is a cheap workaround for JS lacking customizable hash-functions and equality-testing-functions.
import { Function } from "./metacircular.js";
import { DefaultMap } from "./util.js";
// Global store of function types:
const fnTypes = new DefaultMap(() => new Map());
export const fnType = ({in: inType, out: outType}) => {
const m2 = fnTypes.getdefault(inType, true);
if (m2.has(outType)) {
return m2.get(outType);
}
else {
const fnType = {in: inType, out: outType};
m2.set(outType, fnType);
return fnType;
}
};
const listTypeRegistry = new DefaultMap(elementType => ({listOf: elementType}));
const genericTypeRegistry = new DefaultMap(underlyingType => ({generic: underlyingType}));
const fnTypeRegistry = new DefaultMap(inType => new DefaultMap(outType => ({in: inType, out: outType})));
const productTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({operator: "product", leftType, rightType})));
const sumTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({operator: "sum", leftType, rightType})));
export const lsType = listTypeRegistry.getdefault.bind(listTypeRegistry);
export const genericType = genericTypeRegistry.getdefault.bind(genericTypeRegistry);
export const fnType = ({in: inType, out: outType}) => fnTypeRegistry.getdefault(inType, true).getdefault(outType, true);
export const sumType = (leftType, rightType) => sumTypeRegistry.getdefault(leftType, true).getdefault(rightType, true);
export const prodType = (leftType, rightType) => productTypeRegistry.getdefault(leftType, true).getdefault(rightType, true);
// Wrapper around function below.
export const typedFnType = (instance, callback) => {
const [t, typesOfFns] = typedFnType2(callback);
const res = [
{i: instance, t},
...typesOfFns,
];
return res;
}
// Create a function type, and also create Type-links for the function type (being typed by Function) and for all the nested function types. Saves a lot of code writing.
export const typedFnType2 = callback => {
const fnTs = [];
const wrappedFnType = ({in: inType, out: outType}) => {
console.log('wrappedFnType called')
const fnT = fnType({in: inType, out: outType});
fnTs.push(fnT);
return fnT;
}
const t = callback(wrappedFnType); // force evaluation
return [
{i: instance, t: callback(wrappedFnType)},
...fnTs.map(fnT => ({i: fnT, t: Function})),
t,
fnTs.map(fnT => ({i: fnT, t: Function})),
];
}
// Global store of list types:
const listTypes = new Map();
export const lsType = (elementType) => {
if (listTypes.has(elementType)) {
// only generate each list type once
// this would not be necessary if we could define our own equality and hash functions on objects in JavaScript.
return listTypes.get(elementType);
}
else {
const type = {
listOf: elementType,
};
listTypes.set(elementType, type);
return type;
}
}
// Global store of product types:
const productTypes = new DefaultMap(() => new Map());
export const prodType = (leftType, rightType) => {
const m2 = productTypes.getdefault(leftType, true);
if (m2.has(rightType)) {
return m2.get(rightType);
}
else {
const pt = Symbol(`${leftType.toString()} × ${rightType.toString()}`);
m2.set(rightType, pt);
return pt;
}
}
// Global store of sum types:
const sumTypes = new DefaultMap(() => new Map());
export const sumType = (leftType, rightType) => {
const m2 = sumTypes.getdefault(leftType, true);
if (m2.has(rightType)) {
return m2.get(rightType);
}
else {
const st = Symbol(`${leftType.toString()} + ${rightType.toString()}`);
m2.set(rightType, st);
return st;
}
}
// const genericTypes = new Map();
// export const genericType = (underlyingType) => {
// if (genericTypes.has(underlyingType)) {
// // only generate each list type once
// // this would not be necessary if we could define our own equality and hash functions on objects in JavaScript.
// return genericTypes.get(underlyingType);
// }
// else {
// const type = {
// generic: underlyingType,
// };
// genericTypes.set(underlyingType, type);
// return type;
// }
// }