Add product and sum types

This commit is contained in:
Joeri Exelmans 2025-03-17 17:54:42 +01:00
parent 6023efc295
commit 574651ccb7
18 changed files with 209 additions and 157 deletions

View file

@ -1,15 +0,0 @@
import { DefaultMap } from "./util.js";
const mapping = new DefaultMap(() => new Map());
export const fnType = ({in: inType, out: 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;
}
}

View file

@ -0,0 +1,27 @@
import { fnType } from "../type_registry.js";
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 = fnType({in: Int, out: Int });
const Int_to_Bool = fnType({in: Int, out: Bool});
export const Int_to_Int_to_Int = fnType({in: Int, out: Int_to_Int});
export const Int_to_Int_to_Bool = fnType({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 },
];

View file

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

View file

@ -1,30 +0,0 @@
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,
];

View file

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

33
lib/values.js Normal file
View file

@ -0,0 +1,33 @@
import {Int, Bool, Double} from "../primitives/symbols.js";
import { makeListModule } from "../structures/list_common.js";
import { makeProductType } from "../structures/product.js";
import { makeSumType } from "../structures/sum.js";
import { getListType, prodType, sumType } from "../type_registry.js";
const ListOfInt = getListType(Int);
const ListOfIntModule = makeListModule(Int);
const ListOfListOfInt = getListType(ListOfInt);
const ListOfListOfIntModule = makeListModule(ListOfInt);
export const ModuleValues = [
{i: 0n, t: Int},
{i: 42n, t: Int},
{i: false, t: Bool},
{i: 3.14159265359, t: Double},
{i: {l:[42n, 43n]} , t: ListOfInt},
{i: {l:[{l:[42n, 43n]}, {l:[999n]}]}, t: ListOfListOfInt},
// i'm lazy
...ListOfIntModule,
...ListOfListOfIntModule,
...makeProductType(Int, Bool),
...makeSumType(Int, Bool),
{i: {left: 42n, right: true}, t: prodType(Int, Bool)},
{i: {variant: "L", value: 100n}, t: sumType(Int, Bool)},
];

92
main.js
View file

@ -6,10 +6,11 @@ import {Int_to_Int_to_Int, ModuleInt, mulInt} from "./primitives/int.js";
import {Int} from "./primitives/symbols.js"; import {Int} from "./primitives/symbols.js";
import { makeSquare } from "./lib/square.js"; import { makeSquare } from "./lib/square.js";
import {makeIdFn} from "./lib/id.js"; import {makeIdFn} from "./lib/id.js";
import {ModuleLiterals} from "./lib/literals.js"; import {ModuleValues} from "./lib/values.js";
import {DefaultMap} from "./util.js"; import {DefaultMap} from "./util.js";
import { ModuleModule } from "./structures/module.js"; import { ModuleModule } from "./structures/module.js";
import { ModuleList } from "./structures/list.js";
class Context { class Context {
constructor(mod) { constructor(mod) {
@ -24,46 +25,10 @@ class Context {
this.addInstance({i, t}); 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 callFunctionOnEveryValue = (fn, fnType, indent=1) => {
const inType = getIn(fnType); const inType = getIn(fnType);
const outType = getOut(fnType); const outType = getOut(fnType);
console.log();
console.log("--".repeat(indent), fn, ':', fnType); console.log("--".repeat(indent), fn, ':', fnType);
for (const i of this.instances.getdefault(inType)) { for (const i of this.instances.getdefault(inType)) {
const result = fn(i); const result = fn(i);
@ -83,54 +48,6 @@ class Context {
} }
} }
// 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}) { addInstance({i, t}) {
@ -165,6 +82,7 @@ const ctx = new Context([
...makeIdFn(Int), ...makeIdFn(Int),
...makeSquare({i: mulInt, t: Int_to_Int_to_Int}), ...makeSquare({i: mulInt, t: Int_to_Int_to_Int}),
...makeSquare({i: mulDouble, t: Double_to_Double_to_Double}), ...makeSquare({i: mulDouble, t: Double_to_Double_to_Double}),
...ModuleLiterals, ...ModuleList,
...ModuleValues,
...ModuleModule, ...ModuleModule,
]); ]);

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import { fnType } from "../function_registry.js"; import { fnType, getListType } from "../type_registry.js";
import {Type, Function} from "../metacircular.js"; import {Type, Function} from "../metacircular.js";
import { makeListModule } from "./list_common.js"; import { makeListModule } from "./list_common.js";
import { Module } from "./module.js";
const Type_to_Type = fnType({in: Type, out: Type}); const Type_to_Type = fnType({in: Type, out: Type});
const Type_to_Module = fnType({in: Type, out: Module}); const Type_to_Module = fnType({in: Type, out: Module});

View file

@ -1,22 +1,7 @@
import { fnType } from "../function_registry.js"; import { fnType, getListType } from "../type_registry.js";
import {Type, Function} from "../metacircular.js"; import {Type, Function} from "../metacircular.js";
import {Int} from "../primitives/symbols.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 => { export const makeListModule = elementType => {
// List<elementType> type depends on elementType // List<elementType> type depends on elementType
// generating it another time, will give the same type (structurally equivalent): // generating it another time, will give the same type (structurally equivalent):

View file

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

29
structures/product.js Normal file
View file

@ -0,0 +1,29 @@
import { fnType, prodType } from "../type_registry.js";
import { Function } from "../metacircular.js";
// In JS, all products are encoded in the same way:
const constructor = left => right => ({left, right});
const left = product => product.left;
const right = product => product.right;
// Given two types A and B, create the type (A × B).
export const makeProductType = (leftType, rightType) => {
const productType = prodType(leftType, rightType);
const leftFnType = fnType({in: productType, out: leftType});
const rightFnType = fnType({in: productType, out: rightType});
const constructorBoundType = fnType({in: rightType, out: productType});
const constructorType = fnType({in: leftType, out: constructorBoundType});
return [
{i: left , t: leftFnType},
{i: right , t: rightFnType},
{i: leftFnType , t: Function},
{i: rightFnType, t: Function},
{i: constructor , t: constructorType},
{i: constructorType , t: Function},
{i: constructorBoundType, t: Function},
];
};

41
structures/sum.js Normal file
View file

@ -0,0 +1,41 @@
import { fnType, prodType, sumType } from "../type_registry.js";
import { Function, Type } from "../metacircular.js";
import { Module } from "./module.js";
const constructorLeft = left => ({variant: "L", value: left });
const constructorRight = right => ({variant: "R", value: right});
const match = sum => handlers => sum.variant === "L"
? handlers.left(sum.value)
: handlers.right(sum.value);
// Given two types A and B, create the type (A + B).
export const makeSumType = (leftType, rightType) => {
const sType = sumType(leftType, rightType);
const constructorLeftType = fnType({in: leftType , out: sType});
const constructorRightType = fnType({in: rightType, out: sType});
// For each possible return type, the match function has a different signature.
// We thus define a function that creates a properly typed match function.
const makeMatchFn = returnType => {
const handlersType = prodType(
fnType({in: leftType , out: returnType}), // handler function for left variant
fnType({in: rightType, out: returnType}), // handler function for right variant
);
const matchFnType = fnType({in: sType, out: fnType({in: handlersType, out: returnType})});
return [
{i: match , t: matchFnType},
{i: matchFnType, t: Function},
];
};
return [
{i: constructorLeft , t: constructorLeftType},
{i: constructorRight , t: constructorRightType},
{i: constructorLeftType , t: Function},
{i: constructorRightType, t: Function},
{i: makeMatchFn, t: fnType({in: Type, out: Module})},
];
};

62
type_registry.js Normal file
View file

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

View file

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