Add product and sum types
This commit is contained in:
parent
6023efc295
commit
574651ccb7
18 changed files with 209 additions and 157 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
27
interfaces/serializable.js
Normal file
27
interfaces/serializable.js
Normal 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 },
|
||||
];
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { fnType } from "../function_registry.js";
|
||||
import { fnType } from "../type_registry.js";
|
||||
import {Function} from "../metacircular.js";
|
||||
|
||||
// import {Typed} from "../typed.js";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
];
|
||||
|
|
@ -2,7 +2,7 @@ import {Function, getIn, getOut} from "../metacircular.js";
|
|||
import {Module} from "../structures/module.js";
|
||||
import { deepEqual } from "../util.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";
|
||||
|
||||
|
|
|
|||
33
lib/values.js
Normal file
33
lib/values.js
Normal 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
92
main.js
|
|
@ -6,10 +6,11 @@ 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 {ModuleValues} from "./lib/values.js";
|
||||
|
||||
import {DefaultMap} from "./util.js";
|
||||
import { ModuleModule } from "./structures/module.js";
|
||||
import { ModuleList } from "./structures/list.js";
|
||||
|
||||
class Context {
|
||||
constructor(mod) {
|
||||
|
|
@ -24,46 +25,10 @@ class Context {
|
|||
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();
|
||||
console.log("--".repeat(indent), fn, ':', fnType);
|
||||
for (const i of this.instances.getdefault(inType)) {
|
||||
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}) {
|
||||
|
|
@ -165,6 +82,7 @@ const ctx = new Context([
|
|||
...makeIdFn(Int),
|
||||
...makeSquare({i: mulInt, t: Int_to_Int_to_Int}),
|
||||
...makeSquare({i: mulDouble, t: Double_to_Double_to_Double}),
|
||||
...ModuleLiterals,
|
||||
...ModuleList,
|
||||
...ModuleValues,
|
||||
...ModuleModule,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { fnType } from "./function_registry.js";
|
||||
import { fnType } from "./type_registry.js";
|
||||
|
||||
export const Type = Symbol('Type');
|
||||
export const Function = Symbol('Function');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { fnType } from "../function_registry.js";
|
||||
import { fnType } from "../type_registry.js";
|
||||
import {Type, Function} from "../metacircular.js";
|
||||
import {Bool} from "./symbols.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { fnType } from "../function_registry.js";
|
||||
import { fnType } from "../type_registry.js";
|
||||
import {Type, Function} from "../metacircular.js";
|
||||
|
||||
import {Bool, Double} from "./symbols.js";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { fnType } from "../function_registry.js";
|
||||
import { fnType } from "../type_registry.js";
|
||||
import {Type, Function} from "../metacircular.js";
|
||||
|
||||
import {Bool, Int} from "./symbols.js";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { fnType } from "../function_registry.js";
|
||||
import { fnType, getListType } from "../type_registry.js";
|
||||
import {Type, Function} from "../metacircular.js";
|
||||
import { makeListModule } from "./list_common.js";
|
||||
import { Module } from "./module.js";
|
||||
|
||||
const Type_to_Type = fnType({in: Type, out: Type});
|
||||
const Type_to_Module = fnType({in: Type, out: Module});
|
||||
|
|
|
|||
|
|
@ -1,22 +1,7 @@
|
|||
import { fnType } from "../function_registry.js";
|
||||
import { fnType, getListType } from "../type_registry.js";
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { getListType, makeListModule } from "./list_common.js";
|
||||
import { makeListModule } from "./list_common.js";
|
||||
import { Typed } from "../typed.js";
|
||||
import { getListType } from "../type_registry.js";
|
||||
|
||||
export const Module = getListType(Typed); // a Module is a list of Typeds
|
||||
|
||||
|
|
|
|||
29
structures/product.js
Normal file
29
structures/product.js
Normal 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
41
structures/sum.js
Normal 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
62
type_registry.js
Normal 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;
|
||||
}
|
||||
}
|
||||
2
typed.js
2
typed.js
|
|
@ -1,4 +1,4 @@
|
|||
import { fnType } from "./function_registry.js";
|
||||
import { fnType } from "./type_registry.js";
|
||||
import {Type, Function} from "./metacircular.js";
|
||||
|
||||
export const Typed = Symbol('Typed');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue