refactor code: move everything from type_registry to "most appropriate" modules

This commit is contained in:
Joeri Exelmans 2025-03-20 18:12:30 +01:00
parent 4ca60784aa
commit 5283be608b
22 changed files with 160 additions and 164 deletions

View file

@ -1,6 +1,21 @@
import { fnType, lsType } from "../type_registry.js";
import { lsType } from "../structures/list_common.js";
import { fnType } from "../metacircular.js";
import { deepEqual, pretty } from "../util.js";
const genericTypeRegistry = new DefaultMap(underlyingType => ({generic: underlyingType}));
// type constructor for generic kinds,
// for instance:
// a -> a -> Bool
// is typed by
// genericType(Function)
export const genericType = underlyingType => genericTypeRegistry.getdefault(underlyingType, true);
// constructor for generic types
// for instance, the type:
// a -> a -> Bool
// is created by
// makeGeneric(a => fnType({in: a, out: fnType({in: a, out: Bool})}))
export const makeGeneric = callback => {
// type variables to make available:
const a = Symbol('a');
@ -32,14 +47,11 @@ const occurring = (type, typeVars) => {
return new Set();
}
// merge_int(1, 2) => conflict(1,2)
// merge_list_of_int([1], [2]) => [conflict(1,2)]
// Currently very ad-hoc.
// merge {i: [1], t: List_of_Int} ->
// Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog:
// 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
export const matchGeneric = (
export const unify = (
{typeVars: formalTypeVars, type: formalType},
{typeVars: actualTypeVars, type: actualType},
) => {
@ -71,8 +83,8 @@ export const matchGeneric = (
}
else {
// both are function type
const inType = matchGeneric({typeVars: formalTypeVars, type: formalType.in}, {typeVars: actualTypeVars, type: actualType.in});
const outType = matchGeneric({typeVars: formalTypeVars, type: formalType.out}, {typeVars: actualTypeVars, type: actualType.out});
const inType = unify({typeVars: formalTypeVars, type: formalType.in}, {typeVars: actualTypeVars, type: actualType.in});
const outType = unify({typeVars: formalTypeVars, type: formalType.out}, {typeVars: actualTypeVars, type: actualType.out});
// check for conflicts between 'in' and 'out' subsitutions
for (const [typeVar, actual] of inType.substitutions) {
if (outType.substitutions.has(typeVar)) {
@ -104,7 +116,7 @@ export const matchGeneric = (
}
else {
// both are list type
const elementType = matchGeneric(
const elementType = unify(
{typeVars: formalTypeVars, type: formalType.listOf},
{typeVars: actualTypeVars, type: actualType.listOf});
return {
@ -116,12 +128,16 @@ export const matchGeneric = (
};
}
}
if (formalType.numDict !== undefined) {
}
throw new Error("i don't know what to do :(")
};
export const matchConcrete = ({typeVars, type: formalType}, actualType) => {
return matchGeneric({typeVars, type: formalType}, {typeVars: new Set(), type: actualType});
};
// export const matchConcrete = ({typeVars, type: formalType}, actualType) => {
// return unify({typeVars, type: formalType}, {typeVars: new Set(), type: actualType});
// };
export const substitute = (type, substitutions) => {
if (substitutions.has(type)) {
@ -142,7 +158,7 @@ export const substitute = (type, substitutions) => {
}
export const assign = (genFnType, paramType) => {
const matchedInType = matchGeneric({
const matchedInType = unify({
typeVars: genFnType.typeVars,
type: genFnType.type.in,
}, paramType);

View file

@ -1,20 +1,21 @@
import { Bool, Int } from "../primitives/symbols.js";
import { fnType, lsType } from "../type_registry.js";
import { assign, makeGeneric, matchConcrete, matchGeneric } from "./generics.js";
import { lsType } from "../structures/list_common.js";
import { fnType } from "../metacircular.js";
import { assign, makeGeneric, unify } from "./generics.js";
// a -> Int
const a_to_Int = makeGeneric(a => fnType({in: a, out: Int}));
// Bool -> Int
const Bool_to_Int = fnType({in: lsType(Bool), out: Int});
const Bool_to_Int = makeGeneric(() => fnType({in: lsType(Bool), out: Int}));
// should be: Bool -> Int
console.log(matchConcrete(a_to_Int, Bool_to_Int));
console.log(unify(a_to_Int, Bool_to_Int));
// (a -> a) -> b
const fnType2 = makeGeneric((a,b) => fnType({in: fnType({in: a, out: a}), out: b}));
// (Bool -> Bool) -> a
const fnType3 = makeGeneric(a => fnType({in: fnType({in: Bool, out: Bool}), out: a}));
// should be: (Bool -> Bool) -> a
console.log(matchGeneric(fnType2, fnType3));
console.log(unify(fnType2, fnType3));
// (a -> b) -> [a] -> [b]
const mapFnType = makeGeneric((a,b) =>

View file

@ -1,4 +1,4 @@
import { fnType } from "../type_registry.js";
import { fnType } from "../metacircular.js";
import {Function} from "../metacircular.js";
// import {Typed} from "../typed.js";

View file

@ -1,7 +1,8 @@
import { Double } from "../primitives/symbols.js";
import { nominalType, NominalType } from "../structures/nominal_type.js";
import { makeProductType } from "../structures/product.js";
import { prodType, typedFnType } from "../type_registry.js";
import { typedFnType } from "../metacircular.js";
import { prodType } from "../structures/product.js";
const PointCartesian2D = nominalType(
"PointCartesian2D")

View file

@ -2,7 +2,7 @@ import {Function, getIn, getOut} from "../metacircular.js";
import {Module} from "../structures/list_types/module.js";
import { deepEqual } from "../util.js";
import { Typed } from "../typed.js";
import { fnType } from "../type_registry.js";
import { fnType } from "../metacircular.js";
// import {Num, NumDict, getType, getMul} from "../typeclasses/num.js";

View file

@ -13,7 +13,9 @@ import {Int, Bool, Double, Byte} from "./primitives/symbols.js";
import { makeListModule } from "./structures/list_common.js";
import { makeProductType } from "./structures/product.js";
import { makeSumType } from "./structures/sum.js";
import { lsType, prodType, sumType } from "./type_registry.js";
import { sumType } from "./structures/sum.js";
import { prodType } from "./structures/product.js";
import { lsType } from "./structures/list_common.js";
class Context {
constructor(mod) {

View file

@ -1,4 +1,12 @@
import { fnType } from "./type_registry.js";
import { DefaultMap } from "./util.js";
// The registry ensures that we never accidentally create more than one JS object for the same function type.
// It is a cheap workaround for JS lacking customizable hash-functions and equality-testing-functions.
// This same pattern is repeated throughout the code for all non-nullary type constructors (list, sum, product, ...)
const fnTypeRegistry = new DefaultMap(inType => new DefaultMap(outType => ({ in: inType, out: outType })));
// type constructor for function types
export const fnType = ({ in: inType, out: outType }) => fnTypeRegistry.getdefault(inType, true).getdefault(outType, true);
export const Type = Symbol('Type');
export const Function = Symbol('Function');
@ -29,3 +37,30 @@ export const ModuleMetaCircular = {l:[
{i: getIn , t: fnType({in: Function, out: Type})},
{i: getOut, t: fnType({in: Function, out: Type})},
]};
// 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 [
t,
fnTs.map(fnT => ({ i: fnT, t: Function })),
];
};

View file

@ -1,4 +1,4 @@
import { fnType } from "../type_registry.js";
import { fnType } from "../metacircular.js";
import {Type, Function} from "../metacircular.js";
import {Bool} from "./symbols.js";

View file

@ -1,4 +1,4 @@
import { fnType } from "../type_registry.js";
import { fnType } from "../metacircular.js";
import {Type, Function} from "../metacircular.js";
import {Bool, Double} from "./symbols.js";

View file

@ -1,4 +1,4 @@
import { fnType } from "../type_registry.js";
import { fnType } from "../metacircular.js";
import {Type, Function} from "../metacircular.js";
import {Bool, Int} from "./symbols.js";

View file

@ -1,4 +1,5 @@
import { fnType, lsType } from "../type_registry.js";
import { lsType } from "./list_common.js";
import { fnType } from "../metacircular.js";
import {Type, Function} from "../metacircular.js";
import { makeListModule } from "./list_common.js";
import { Module } from "./list_types/module.js";

View file

@ -1,6 +1,12 @@
import { fnType, lsType } from "../type_registry.js";
import { fnType } from "../metacircular.js";
import {Type, Function} from "../metacircular.js";
import {Int, Byte} from "../primitives/symbols.js";
import { DefaultMap } from "../util.js";
const listTypeRegistry = new DefaultMap(elementType => ({ listOf: elementType }));
// type constructor
export const lsType = elementType => listTypeRegistry.getdefault(elementType, true);
// 'normal' implementation
const emptyList = {l:[]};

View file

@ -1,6 +1,6 @@
import { makeListModule } from "../list_common.js";
import { Typed } from "../../typed.js";
import { lsType } from "../../type_registry.js";
import { lsType } from "../list_common.js";
export const Module = lsType(Typed); // a Module is a list of Typeds

View file

@ -1,5 +1,5 @@
import { Char } from "../../primitives/symbols.js";
import { lsType } from "../../type_registry.js";
import { lsType } from "../list_common.js";
import { makeListModule } from "../list_common.js";
export const String = lsType(Char);

View file

@ -1,6 +1,6 @@
import { Type } from "../metacircular.js";
import { String } from "../structures/list_types/string.js";
import { prodType } from "../type_registry.js";
import { prodType } from "./product.js";
import { makeProductType } from "./product.js";
export const NominalType = prodType(String, Type);

View file

@ -1,5 +1,11 @@
import { fnType, prodType } from "../type_registry.js";
import { fnType } from "../metacircular.js";
import { Function, Type } from "../metacircular.js";
import { DefaultMap } from "../util.js";
const productTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ operator: "product", leftType, rightType })));
// type constructor
export const prodType = (leftType, rightType) => productTypeRegistry.getdefault(leftType, true).getdefault(rightType, true);
// In JS, all products are encoded in the same way:
const constructor = left => right => ({left, right});

View file

@ -1,7 +1,14 @@
import { fnType, prodType, sumType } from "../type_registry.js";
import { sumType } from "../type_registry.js";
import { prodType } from "./product.js";
import { fnType } from "../metacircular.js";
import { Function, Type } from "../metacircular.js";
import { Module } from "./list_types/module.js";
const sumTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ operator: "sum", leftType, rightType })));
// type constructor
export const sumType = (leftType, rightType) => sumTypeRegistry.getdefault(leftType, true).getdefault(rightType, true);
const constructorLeft = left => ({variant: "L", value: left });
const constructorRight = right => ({variant: "R", value: right});

View file

@ -1,47 +0,0 @@
// 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";
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 [
t,
fnTs.map(fnT => ({i: fnT, t: Function})),
];
}

View file

@ -1,33 +0,0 @@
// import {Type, Function, makeTypedFnInOut, fnOut} from "../metacircular.js";
// import {Bool} from "../primitives/symbols.js";
// export const Conformable = Symbol('Conformable');
// export const conformanceCheck = Symbol('conformanceCheck');
// export const ModuleConformable = [
// {i: Conformable , t: Type},
// {i: conformanceCheck, t: Function},
// // Note: outType is just 'Type', we cannot use Bool because a function that has type 'conformanceCheck' will have outType Bool, therefore conformanceCheck must return the type of Bool, which is Type.
// ...makeTypedFnInOut({f: conformanceCheck, inType: Conformable, outType: Type}),
// ];
// ////////////////////////////////////////////////////////
// // Conformance check functions are themselves conformance checkable
// // This is because we want to restrict their return type to Bool
// const conformanceCheckIsConform = fn => {
// return fnOut(fn) === Bool;
// };
// const conformanceCheckIsConformFn = {name: "isConform", inType: conformanceCheck, outType: Bool, fn: conformanceCheckIsConform};
// export const ModuleConformanceCheckConforms = [
// // instances of conformanceCheck (= 'isConform'-functions) are themselves conformance checkable
// {i: conformanceCheck, t: Conformable},
// {i: conformanceCheckIsConformFn, t: conformanceCheck},
// {i: conformanceCheckIsConformFn, t: Function},
// ];

View file

@ -1,52 +1,54 @@
// import {Type, Function, makeTypedFnInOut, fnIn, fnOut} from "../metacircular.js";
// import {Conformable, conformanceCheck} from "./conformable.js";
// import {Bool} from "../primitives/symbols.js";
// import {deepEqual} from "../util.js";
import { makeGeneric } from "../generics/generics";
import { addDouble, mulDouble } from "../primitives/double";
import { addInt, mulInt } from "../primitives/int";
import { typedFnType, typedFnType2 } from "../metacircular";
// export const Num = Symbol('Num');
// export const NumDict = Symbol('NumDict');
// // export const addType = Symbol('addType');
// // export const addBoundType = Symbol('addBoundType');
// // export const mulType = Symbol('mulType');
// // export const mulBoundType = Symbol('mulBoundType');
const numDictTypeRegistry = new DefaultMap(a => ({numDict: a}));
export const numDictType = a => numDictTypeRegistry.getdefault(a, true);
// // export const getType = numDict => numDict.type;
export const getAdd = numDict => numDict.add;
export const getMul = numDict => numDict.mul;
// export const getAdd = numDict => numDict.add;
// export const getMul = numDict => numDict.mul;
// getAdd and getMul have same (generic) type:
const [getAddMulFnType, typesOfFns] = typedFnType2(fnType =>
makeGeneric(a => fnType({
in: numDictType(a),
out: fnType({
in: a,
out: fnType({in: a, out: a}),
}),
})));
// // const addOrMulIsConform = fn => {
// // // function signature must be: a -> a -> a
// // const i0 = fnIn(fn);
// // const o0 = fnOut(fn);
// // const i1 = fnIn(o0);
// // const o1 = fnOut(o0);
// // return deepEqual(i0, i1) && deepEqual(i1, o1);
// // };
export const ModuleNum = {l:[
...typedFnType(numDictType, fnType => fnType({in: Type, out: Type})),
// // const addIsConform = {name: "isConform", inType: addType, outType: Bool, fn: addOrMulIsConform};
// // const mulIsConform = {name: "isConform", inType: mulType, outType: Bool, fn: addOrMulIsConform};
{i: getAdd, t: getAddMulFnType},
{i: getMul, t: getAddMulFnType},
// export const ModuleNum = [
// {i: Num, t: Type},
// {i: NumDict, t: Type},
// {i: addType, t: Type},
// {i: addBoundType, t: Type},
// {i: mulType, t: Type},
// {i: mulBoundType, t: Type},
// {i: {name: "getType", inType: NumDict, outType: Num, fn: getType}, t: Function},
// {i: {name: "getAdd", inType: NumDict, outType: addType, fn: getAdd }, t: Function},
// {i: {name: "getMul", inType: NumDict, outType: mulType, fn: getMul }, t: Function},
// ...makeTypedFnInOut({f: addType , inType: Num, outType: addBoundType}),
// ...makeTypedFnInOut({f: addBoundType, inType: Num, outType: Num }),
// ...makeTypedFnInOut({f: mulType , inType: Num, outType: mulBoundType}),
// ...makeTypedFnInOut({f: mulBoundType, inType: Num, outType: Num }),
...typesOfFns,
]};
// // conformance checking type class
// {i: addType, t: Conformable},
// {i: mulType, t: Conformable},
// {i: addIsConform, t: Function},
// {i: addIsConform, t: conformanceCheck},
// {i: mulIsConform, t: Function},
// {i: mulIsConform, t: conformanceCheck},
// ];
const IntNumDict = {
add: addInt,
mul: mulInt,
};
const DoubleNumDict = {
add: addDouble,
mul: mulDouble,
}
export const ModuleNumInstances = {l:[
{i: IntNumDict , t: NumDict},
{i: DoubleNumDict, t: NumDict},
]};
// mapping from type to type class implementation
// in Haskell, such a mapping is global (for the entire application being compiled), and every type can implement every type class at most once.
// in Lean, such mappings can be local, and there can be multiple implementations per (type, type class).
// We have to follow Lean's approach, because in DOPE, there is no such thing as a "global scope". Every dependency is explicit, and type class resolution is just a function that always depends on a specific mapping:
export const NumInstances = new Map([
[Int , IntNumDict ],
[Double, DoubleNumDict],
]);

View file

@ -1,4 +1,4 @@
import { fnType } from "./type_registry.js";
import { fnType } from "./metacircular.js";
import {Type, Function} from "./metacircular.js";
export const Typed = Symbol('Typed');

View file

@ -22,7 +22,6 @@ export function deepEqual(a, b) {
return true;
}
export class DefaultMap {
constructor(defaultValue, ...rest) {
this.defaultValue = defaultValue;