This commit is contained in:
Joeri Exelmans 2025-03-23 09:15:37 +01:00
parent afd78c3b3e
commit 29d20b2273
25 changed files with 369 additions and 469 deletions

51
structures/function.js Normal file
View file

@ -0,0 +1,51 @@
import { DefaultMap } from "../util.js";
const symbolFunction = Symbol('Function');
// 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 => ({
symbol: symbolFunction,
params: [inType, outType],
})));
// type constructor for function types
export const fnType = inType => outType => fnTypeRegistry.getdefault(inType, true).getdefault(outType, 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 }) => {
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 })),
];
};
export const ModuleFunction = {l:[
// binary type constructor: Type -> Type -> Type
...typedFnType(fnType, fnType => fnType
/* in */ (Type)
/* out */ (fnType
/* in */ (Type)
/* out */ (Type)
)
),
]};

View file

@ -1,16 +1,51 @@
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";
import { typedFnType } from "./function.js";
import { Type } from "../metacircular.js";
import { Int } from "../primitives/symbols.js";
import { DefaultMap } from "../util.js";
import { makeGeneric } from "../generics/generics.js";
const Type_to_Type = fnType({in: Type, out: Type});
const Type_to_Module = fnType({in: Type, out: Module});
const symbolList = Symbol('List');
const listTypeRegistry = new DefaultMap(elementType => ({
symbol: symbolList,
params: [elementType],
}));
// type constructor
export const lsType = elementType => listTypeRegistry.getdefault(elementType, true);
// 'normal' implementation
const emptyList = {l:[]};
const get = ls => i => ls.l[i];
const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)});
// const push = ls => elem => ({l:ls.l.concat([elem])});
export const ModuleList = {l:[
{i: lsType , t: Type_to_Type},
{i: Type_to_Type , t: Function},
// Type -> Type
...typedFnType(lsType, fnType => fnType
/* in */ (Type)
/* out */ (Type)
),
{i: makeListModule, t: Type_to_Module},
{i: Type_to_Module, t: Function},
// [a]
{i: emptyList, t: makeGeneric(a => lsType(a))},
// [a] -> Int -> a
...typedFnType(get, fnType => makeGeneric(a => fnType
/* in */ (lsType(a))
/* out */ (fnType
/* in */ (Int)
/* out */ (a)
))),
// [a] -> Int -> a -> [a]
...typedFnType(put, fnType => makeGeneric(a => fnType
/* in */ (lsType(a))
/* out */ (fnType
/* in */ (Int)
/* out */ (fnType
/* in */ (a)
/* out */ (lsType(a))
)
))),
]};

View file

@ -1,75 +0,0 @@
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:[]};
const get = ls => i => ls.l[i];
const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)});
// const push = ls => elem => ({l:ls.l.concat([elem])});
const byteListImpl = {
// specialization
emptyList: new Uint8Array(),
get: ls => i => ls[i],
put: ls => i => elem => {
res = new Uint8Array(ls); // creates copy
res[i] = elem;
return res;
}
}
export const makeListModule = elementType => {
// List<elementType> type depends on elementType
// generating it another time, will give the same type (structurally equivalent):
const ListOfElement = lsType(elementType);
const getFnType1 = fnType({in: Int , out: elementType});
const getFnType = fnType({in: ListOfElement, out: getFnType1});
const putFnType2 = fnType({in: elementType , out: ListOfElement});
const putFnType1 = fnType({in: Int , out: putFnType2});
const putFnType = fnType({in: ListOfElement, out: putFnType1});
// const pushFnType1 = fnType({in: elementType , out: ListOfElement});
// const pushFnType = fnType({in: ListOfElement, out: pushFnType1});
const common = [
{i: ListOfElement, t: Type},
{i: getFnType , t: Function},
{i: getFnType1 , t: Function},
{i: putFnType , t: Function},
{i: putFnType1, t: Function},
{i: putFnType2, t: Function},
// {i: pushFnType , t: Function},
// {i: pushFnType1, t: Function},
];
if (elementType === Byte) {
// specialization: use Uint8Array instead of JS array
return {l:[
...common,
{i: byteListImpl.emptyList , t: ListOfElement},
{i: byteListImpl.get , t: getFnType},
{i: byteListImpl.put , t: putFnType},
]};
}
else {
return {l:[
...common,
{i: emptyList , t: ListOfElement},
{i: get , t: getFnType},
{i: put , t: putFnType},
// {i: push , t: pushFnType},
]};
}
};

View file

@ -1,7 +1,8 @@
import { makeListModule } from "../list_common.js";
import { makeListModule } from "../list.js";
import { Typed } from "../../typed.js";
import { lsType } from "../list_common.js";
import { lsType } from "../list.js";
// just an alias
export const Module = lsType(Typed); // a Module is a list of Typeds
export const ModuleModule = makeListModule(Typed); // the module containing operations on Module

View file

@ -1,7 +1,8 @@
import { Char } from "../../primitives/symbols.js";
import { lsType } from "../list_common.js";
import { makeListModule } from "../list_common.js";
import { lsType } from "../list.js";
import { makeListModule } from "../list.js";
// just an alias
export const String = lsType(Char);
export const ModuleString = makeListModule(Char);

View file

@ -1,37 +1,51 @@
import { fnType } from "../metacircular.js";
import { Function, Type } from "../metacircular.js";
import { makeGeneric } from "../generics/generics.js";
import { Type } from "../metacircular.js";
import { DefaultMap } from "../util.js";
import { typedFnType } from "./function.js";
const productTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ operator: "product", leftType, rightType })));
const symbolProduct = Symbol("Product");
const productTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({
symbol: symbolProduct,
params: [leftType, rightType],
})));
// type constructor
export const prodType = (leftType, rightType) => productTypeRegistry.getdefault(leftType, true).getdefault(rightType, true);
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});
const getLeft = product => product.left;
const getRight = product => product.right;
// Given two types A and B, create the type (A × B).
export const makeProductType = (leftType, rightType) => {
const pType = prodType(leftType, rightType);
export const ModuleProduct = {l: [
// binary type constructor
// Type -> Type -> Type
...typedFnType(prodType, fnType => fnType
(Type)
(fnType
(Type)
(Type)
)
),
const leftFnType = fnType({in: pType, out: leftType});
const rightFnType = fnType({in: pType, out: rightType});
// a -> b -> (a, b)
...typedFnType(constructor, fnType => makeGeneric((a, b) => fnType
(a)
(fnType
(b)
(prodType(a)(b))
)
)),
const constructorBoundType = fnType({in: rightType, out: pType});
const constructorType = fnType({in: leftType , out: constructorBoundType});
// (a, b) -> a
...typedFnType(getLeft, fnType => makeGeneric((a, b) => fnType
(prodType(a)(b))
(a)
)),
return {l:[
{i: pType, t: Type},
{i: getLeft , t: leftFnType},
{i: getRight , t: rightFnType},
{i: leftFnType , t: Function},
{i: rightFnType, t: Function},
{i: constructor , t: constructorType},
{i: constructorType , t: Function},
{i: constructorBoundType, t: Function},
]};
};
// (a, b) -> b
...typedFnType(getRight, fnType => makeGeneric((a, b) => fnType
(prodType(a)(b))
(b)
)),
]};

View file

@ -1,53 +1,60 @@
import { prodType } from "./product.js";
import { fnType } from "../metacircular.js";
import { Function, Type } from "../metacircular.js";
import { Module } from "./list_types/module.js";
import { Type } from "../metacircular.js";
import { DefaultMap } from "../util.js";
import { typedFnType } from "./function.js";
import { makeGeneric } from "../generics/generics.js";
const sumTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ operator: "sum", leftType, rightType })));
const symbolSum = Symbol("Sum");
const sumTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({
symbol: symbolSum,
params: [leftType, rightType],
})));
// type constructor
export const sumType = (leftType, rightType) => sumTypeRegistry.getdefault(leftType, true).getdefault(rightType, true);
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});
// (<uuid>, product(double, double)): product(int, type)
// signature:
// sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType
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);
export const ModuleSum = {l:[
// binary type constructor
// Type -> Type -> Type
...typedFnType(sumType, fnType => fnType
(Type)
(fnType
(Type)
(Type)
),
),
const constructorLeftType = fnType({in: leftType , out: sType});
const constructorRightType = fnType({in: rightType, out: sType});
// a -> a | b
...typedFnType(constructorLeft, fnType => makeGeneric((a, b) => fnType
(a)
(sumType(a)(b))
)),
// 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 {l:[
{i: match , t: matchFnType},
{i: matchFnType, t: Function},
]};
};
// b -> a | b
...typedFnType(constructorRight, fnType => makeGeneric((a, b) => fnType
(b)
(sumType(a)(b))
)),
return {l:[
{i: sType , t: Type},
{i: constructorLeft , t: constructorLeftType},
{i: constructorRight , t: constructorRightType},
{i: constructorLeftType , t: Function},
{i: constructorRightType, t: Function},
{i: makeMatchFn, t: fnType({in: Type, out: Module})},
]};
};
// a | b -> (a -> c, b-> c) -> c
...typedFnType(match, fnType => makeGeneric((a, b, c) => fnType
(sumType(a)(b))
(fnType
(prodType
(fnType(a)(c))
(fnType(b)(c))
)
(c)
)
)),
]};