basic functionality, no generics

This commit is contained in:
Joeri Exelmans 2025-03-14 16:56:37 +01:00
commit a8260f2afb
17 changed files with 615 additions and 0 deletions

15
function_registry.js Normal file
View file

@ -0,0 +1,15 @@
import { DefaultMap } from "./util.js";
const mapping = new DefaultMap(() => new Map());
export const getFnType = (inType, outType) => {
const m2 = mapping.getdefault(inType);
if (m2.has(outType)) {
return m2.get(outType);
}
else {
const fnType = {in: inType, out: outType};
m2.set(outType, fnType);
return fnType;
}
}

38
lib/id.js Normal file
View file

@ -0,0 +1,38 @@
import {Function} from "../metacircular.js";
// import {Typed} from "../typed.js";
// import {Bool} from "../primitives/symbols.js";
// import {deepEqual} from "../util.js";
// import {Conformable, conformanceCheck} from "../typeclasses/conformable.js";
// // Identity function
// const idBoundToType = Symbol('idBoundToType');
// const id = type => x => x;
// const idFn = {name: "id", inType: Type, outType: {inType: Type, outType: Type}, fn: id};
// const idIsConform = {
// name: "isConform",
// inType: idBoundToType,
// outType: Bool,
// fn: fn => deepEqual(fnIn(fn), fnOut(fn))
// }
// export const ModuleId = [
// {i: idBoundToType, t: Type},
// {i: idFn, t: Function},
// ... makeTypedFnInOut({f: idBoundToType, inType: Type, outType: Type}),
// {i: idBoundToType, t: Conformable},
// {i: idIsConform, t: conformanceCheck},
// ];
// generates explicitly typed id-function
export const makeIdFn = typ => {
const Typ_to_Typ = {in: typ, out: typ};
const id = x => x;
return [
{i: id , t: Typ_to_Typ},
{i: Typ_to_Typ, t: Function},
];
};

30
lib/literals.js Normal file
View file

@ -0,0 +1,30 @@
import {Int, Bool, Double} from "../primitives/symbols.js";
import { getListType, makeListModule } from "../structures/list_common.js";
const ListOfBool = getListType(Bool);
const ListOfBoolModule = makeListModule(Bool);
const ListOfInt = getListType(Int);
const ListOfIntModule = makeListModule(Int);
const ListOfListOfInt = getListType(ListOfInt);
const ListOfListOfIntModule = makeListModule(ListOfInt);
export const ModuleLiterals = [
{i: 0n, t: Int},
{i: 42n, t: Int},
{i: false, t: Bool},
{i: 3.14159265359, t: Double},
{i: {l:[42n, 43n]}, t: ListOfInt},
// {i: [[42n, 43n]], t: ListOfListOfInt},
// i'm lazy
...ListOfIntModule,
// ...ListOfBoolModule,
// ...ListOfListOfIntModule,
];

49
lib/square.js Normal file
View file

@ -0,0 +1,49 @@
import {Function, getIn, getOut} from "../metacircular.js";
import {Module} from "../structures/module.js";
import { deepEqual } from "../util.js";
import { Typed } from "../typed.js";
// import {Num, NumDict, getType, getMul} from "../typeclasses/num.js";
// const squareBoundToType = Symbol('squareBoundToType'); // the type of e.g., squareOfInt
// // returning a function + its signature is the only way to make the outType of square depend on its input
// const square = numDict => {
// const typ = getType(numDict);
// const mul = getMul(numDict);
// return {
// name: "squareOf:" + typ.toString(),
// inType: typ,
// outType: typ,
// fn: x => mul(x)(x),
// };
// };
// export const ModuleSquare = [
// {i: squareBoundToType, t: Type},
// {i: {name: "square", inType: NumDict, outType: squareBoundToType, fn: square}, t: Function},
// ...makeTypedFnInOut({f: squareBoundToType, inType: Num, outType: Num}),
// ];
export const makeSquare = ({i: mul, t: mulFunction}) => {
const numType = getIn(mulFunction);
const boundMulFunction = getOut(mulFunction);
if (!deepEqual(getOut(boundMulFunction), numType) || !deepEqual(getIn(boundMulFunction), numType)) {
console.log(getOut(boundMulFunction), getIn(boundMulFunction), numType);
throw new Error("invalid signature");
}
const square = x => mul(x)(x);
const squareFunction = {in: numType, out: numType};
return [
{i: square , t: squareFunction},
{i: squareFunction, t: Function},
];
};
const makeSquareType = {in: Typed, out: Module};
export const ModuleSquare = [
{i: makeSquare , t: makeSquareType},
{i: makeSquareType, t: Function},
];

170
main.js Normal file
View file

@ -0,0 +1,170 @@
import {Function, ModuleMetaCircular, getIn, getOut} from "./metacircular.js";
import {ModuleTyped} from "./typed.js";
import {ModuleBool} from "./primitives/bool.js";
import { Double_to_Double_to_Double, ModuleDouble, mulDouble } from "./primitives/double.js";
import {Int_to_Int_to_Int, ModuleInt, mulInt} from "./primitives/int.js";
import {Int} from "./primitives/symbols.js";
import { makeSquare } from "./lib/square.js";
import {makeIdFn} from "./lib/id.js";
import {ModuleLiterals} from "./lib/literals.js";
import {DefaultMap} from "./util.js";
import { ModuleModule } from "./structures/module.js";
class Context {
constructor(mod) {
// input type -> Function-signature
this.functionsFrom = new DefaultMap(() => []);
this.functionsTo = new DefaultMap(() => []);
this.types = new DefaultMap(() => []);
this.instances = new DefaultMap(() => []);
for (const {i, t} of mod) {
this.addInstance({i, t});
}
// console.log(this.instances.m);
// const callEveryFunction = ({i,t}, indent=0) => {
// for (const fntype of this.functionsFrom.getdefault(t)) {
// for (const fn of this.instances.getdefault(fntype)) {
// console.log(" ", fn, ':', fntype);
// const result = fn(i);
// const resultType = getOut(fntype);
// console.log(" ".repeat(indent), i, '->', result);
// if (this.types.getdefault(resultType).includes(Function)) {
// if (indent < 2) {
// callEveryFunction({i: result, t: resultType}, indent+1)
// }
// }
// }
// }
// };
// const callFunction = (typ, fns) => {
// console.log("Functions callable on", typ, ":");
// for (const fn of fns) {
// console.log(" ", fn);
// for (const FN of this.instances.getdefault(fn)) {
// console.log(" ", FN);
// for (const i of this.instances.getdefault(typ)) {
// const result = FN(i);
// const resultType = getOut(fn);
// console.log(" ", i, '->', result);
// if (this.types.getdefault(resultType).includes(Function)) {
// callFunction(resultType, )
// }
// }
// }
// }
// };
const callFunctionOnEveryValue = (fn, fnType, indent=1) => {
const inType = getIn(fnType);
const outType = getOut(fnType);
console.log("--".repeat(indent), fn, ':', fnType);
for (const i of this.instances.getdefault(inType)) {
const result = fn(i);
console.log("--".repeat(indent+1), i, '->', result);
if (this.types.getdefault(outType).includes(Function)) {
if (indent < 5) {
callFunctionOnEveryValue(result, outType, indent+2);
}
}
}
}
for (const fntypes of this.functionsFrom.m.values()) {
for (const fntype of fntypes) {
for (const fn of this.instances.getdefault(fntype)) {
callFunctionOnEveryValue(fn, fntype);
}
}
}
// for (const [i, ts] of this.types.m.entries()) {
// for (const t of ts) {
// callEveryFunction({i,t});
// }
// }
// // Conformance check
// for (const fn of this.instances.getdefault(conformanceCheck)) {
// const t = fn.inType;
// for (const i of this.instances.getdefault(t)) {
// if (!fn.fn(i)) {
// console.warn(i, 'is not conform', 'to', t);
// }
// else {
// console.info('👍', i, 'conforms to', t);
// }
// }
// }
// const MAXDEPTH = 0;
// let calls = 0;
// const tryEveryFunction = ({i, t}, maxDepth) => {
// calls++;
// for (const fn of this.functionsFrom.getdefault(t)) {
// const outType = getOut(fn);
// // console.log(" ".repeat(MAXDEPTH-maxDepth) + 'calling', fn, 'on', i);
// const result = fn(i);
// console.log(" ".repeat(MAXDEPTH-maxDepth), result, 'should be of type', outType);
// if (outType === TypeLink && result.t === undefined) {
// console.warn(" ".repeat(MAXDEPTH-maxDepth) + ' ^ invalid')
// }
// if (outType === Int && (typeof result) !== "number") {
// console.warn(" ".repeat(MAXDEPTH-maxDepth) + ' ^ invalid')
// }
// this.addInstance({i: result, t: outType});
// if (maxDepth > 0) {
// tryEveryFunction({i: result, t: outType}, maxDepth-1);
// }
// }
// }
// for (const {i, t} of mod) {
// tryEveryFunction({i, t}, MAXDEPTH);
// }
// console.log("calls:", calls);
}
addInstance({i, t}) {
const arr = this.types.getdefault(i, true);
arr.push(t);
const arrI = this.instances.getdefault(t, true);
arrI.push(i);
if (t === Function) {
const arr = this.functionsFrom.getdefault(getIn(i), true);
arr.push(i);
const arrTo = this.functionsTo.getdefault(getOut(i), true);
arrTo.push(i);
}
else {
}
}
}
const ctx = new Context([
...ModuleMetaCircular,
...ModuleTyped,
// ...ModuleConformable,
// ...ModuleConformanceCheckConforms,
// ...ModuleNum,
// ...ModuleEq,
...ModuleBool,
...ModuleInt,
...ModuleDouble,
// ...ModuleSquare,
// ...ModuleList,
...makeIdFn(Int),
...makeSquare({i: mulInt, t: Int_to_Int_to_Int}),
...makeSquare({i: mulDouble, t: Double_to_Double_to_Double}),
...ModuleLiterals,
...ModuleModule,
]);

31
metacircular.js Normal file
View file

@ -0,0 +1,31 @@
import { getFnType } from "./function_registry.js";
export const Type = Symbol('Type');
export const Function = Symbol('Function');
// Implementation of 'in' and 'out' functions,
// to get input/output type of a function signature:
export const getIn = fn => fn.in;
export const getOut = fn => fn.out;
// a module is just a set of typed objects
// each 'typed object' is implicitly an instance of TypeLink (defined below)
export const ModuleMetaCircular = [
// TODO? maybe follow Lean so
// Type.{0} : Type.{1}
// Type.{1} : Type.{2}
// ...
// see: https://lean-lang.org/functional_programming_in_lean/functor-applicative-monad/universes.html
// Type : Type
{i: Type, t: Type},
// Function : Type
{i: Function, t: Type},
// (Function -> Type) : Function
{i: getFnType(Function, Type), t: Function},
{i: getIn , t: getFnType(Function, Type)},
{i: getOut, t: getFnType(Function, Type)},
];

14
primitives/bool.js Normal file
View file

@ -0,0 +1,14 @@
import {Type, Function} from "../metacircular.js";
import {Bool} from "./symbols.js";
const eqBool = x => y => x === y;
const Bool_to_Bool = {in: Bool, out: Bool};
const Bool_to_Bool_to_Bool = {in: Bool, out: Bool_to_Bool};
export const ModuleBool = [
{i: Bool , t: Type },
{i: Bool_to_Bool , t: Function },
{i: Bool_to_Bool_to_Bool , t: Function },
{i: eqBool , t: Bool_to_Bool_to_Bool },
];

26
primitives/double.js Normal file
View file

@ -0,0 +1,26 @@
import {Type, Function} from "../metacircular.js";
import {Bool, Double} from "./symbols.js";
export const addDouble = x => y => x + y;
export const mulDouble = x => y => x * y;
export const eqDouble = x => y => x === y;
const Double_to_Double = {in: Double, out: Double};
const Double_to_Bool = {in: Double, out: Bool};
export const Double_to_Double_to_Double = {in: Double, out: Double_to_Double};
export const Double_to_Double_to_Bool = {in: Double, out: Double_to_Bool};
export const ModuleDouble = [
{i: Double , t: Type },
{i: Double_to_Double_to_Double, t: Function },
{i: Double_to_Double_to_Bool , t: Function },
{i: Double_to_Double , t: Function },
{i: Double_to_Bool , t: Function },
{i: addDouble , t: Double_to_Double_to_Double },
{i: mulDouble , t: Double_to_Double_to_Double },
{i: eqDouble , t: Double_to_Double_to_Bool },
];

26
primitives/int.js Normal file
View file

@ -0,0 +1,26 @@
import {Type, Function} from "../metacircular.js";
import {Bool, Int} from "./symbols.js";
export const addInt = x => y => x + y;
export const mulInt = x => y => x * y;
export const eqInt = x => y => x === y;
const Int_to_Int = {in: Int, out: Int };
const Int_to_Bool = {in: Int, out: Bool};
export const Int_to_Int_to_Int = {in: Int, out: Int_to_Int};
export const Int_to_Int_to_Bool = {in: Int, out: Int_to_Bool};
export const ModuleInt = [
{i: Int , t: Type },
{i: Int_to_Int_to_Int , t: Function },
{i: Int_to_Int_to_Bool , t: Function },
{i: Int_to_Int , t: Function },
{i: Int_to_Bool , t: Function },
{i: addInt , t: Int_to_Int_to_Int },
{i: mulInt , t: Int_to_Int_to_Int },
{i: eqInt , t: Int_to_Int_to_Bool },
];

5
primitives/symbols.js Normal file
View file

@ -0,0 +1,5 @@
// to break up dependency cycles, symbols of primitive types have their own JS module
export const Bool = Symbol('Bool');
export const Int = Symbol('Int');
export const Double = Symbol('Double');

13
structures/list.js Normal file
View file

@ -0,0 +1,13 @@
import {Type, Function} from "../metacircular.js";
import { makeListModule } from "./list_common.js";
const Type_to_Type = {in: Type, out: Type};
const Type_to_Module = {in: Type, out: Module};
export const ModuleList = [
{i: getListType , t: Type_to_Type},
{i: Type_to_Type , t: Function},
{i: makeListModule, t: Type_to_Module},
{i: Type_to_Module, t: Function},
];

46
structures/list_common.js Normal file
View file

@ -0,0 +1,46 @@
import {Type, Function} from "../metacircular.js";
import {Int} from "../primitives/symbols.js";
// Global store of list types:
const listTypes = new Map();
export function getListType(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 = Symbol('ListOf:'+elementType.toString());
listTypes.set(elementType, type);
return type;
}
}
export const makeListModule = elementType => {
// List<elementType> type depends on elementType
// generating it another time, will give the same type (structurally equivalent):
const ListOfElement = getListType(elementType);
const emptyList = {l:[]};
const get = ls => i => ls.l[i];
const getBoundType = {in: Int, out: elementType}
const getType = {in: ListOfElement, out: getBoundType};
const push = ls => elem => ({l:ls.l.concat([elem])});
const pushBoundType = {in: elementType, out: ListOfElement};
const pushType = {in: ListOfElement, out: pushBoundType};
return [
{i: ListOfElement, t: Type},
{i: emptyList , t: ListOfElement},
{i: get , t: getType},
{i: getType , t: Function},
{i: getBoundType , t: Function},
{i: push , t: pushType},
{i: pushType , t: Function},
{i: pushBoundType, t: Function},
];
};

6
structures/module.js Normal file
View file

@ -0,0 +1,6 @@
import { getListType, makeListModule } from "./list_common.js";
import { Typed } from "../typed.js";
export const Module = getListType(Typed); // a Module is a list of Typeds
export const ModuleModule = makeListModule(Typed); // the module containing operations on Module

View file

@ -0,0 +1,33 @@
// 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},
// ];

52
typeclasses/num.js Normal file
View file

@ -0,0 +1,52 @@
// 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";
// 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');
// // export const getType = numDict => numDict.type;
// export const getAdd = numDict => numDict.add;
// export const getMul = numDict => numDict.mul;
// // 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);
// // };
// // const addIsConform = {name: "isConform", inType: addType, outType: Bool, fn: addOrMulIsConform};
// // const mulIsConform = {name: "isConform", inType: mulType, outType: Bool, fn: addOrMulIsConform};
// 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 }),
// // 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},
// ];

19
typed.js Normal file
View file

@ -0,0 +1,19 @@
import { getFnType } from "./function_registry.js";
import {Type, Function} from "./metacircular.js";
export const Typed = Symbol('Typed');
const getInst = lnk => lnk.i;
const getType = lnk => lnk.t;
// const Typed_to_Type = {in: Typed, out: Type};
const Typed_to_Type = getFnType(Typed, Type);
export const ModuleTyped = [
{i: Typed, t: Type},
{i: Typed_to_Type, t: Function},
{i: getInst, t: Typed_to_Type},
{i: getType, t: Typed_to_Type},
];

42
util.js Normal file
View file

@ -0,0 +1,42 @@
// re-inventing the wheel:
export function deepEqual(a, b) {
if (a === b) return true; // <- shallow equality and primitives
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
return false;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) return false;
}
return true;
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (let key of keysA) {
if (!keysB.includes(key) || !deepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
export class DefaultMap {
constructor(defaultValue, ...rest) {
this.defaultValue = defaultValue;
this.m = new Map(rest);
}
getdefault(key, addToMapping=false) {
return this.m.get(key) || (() => {
const val = this.defaultValue();
if (addToMapping)
this.m.set(key, val);
return val;
})();
}
entries() {
return this.m.entries();
}
}