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

View file

@ -1,16 +1,5 @@
import { lsType } from "../structures/list_common.js";
import { fnType } from "../metacircular.js";
import { deepEqual, DefaultMap, pretty } from "../util.js";
import { numDictType } from "../typeclasses/num_type.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);
import { eqType } from "../metacircular.js";
import { pretty, zip } from "../util.js";
// constructor for generic types
// for instance, the type:
@ -19,14 +8,13 @@ export const genericType = underlyingType => genericTypeRegistry.getdefault(unde
// makeGeneric(a => fnType({in: a, out: fnType({in: a, out: Bool})}))
export const makeGeneric = callback => {
// type variables to make available:
const a = Symbol('a');
const b = Symbol('b');
const c = Symbol('c');
const d = Symbol('d');
const e = Symbol('e');
const type = callback(a, b, c, d, e);
const typeVars = ['a', 'b', 'c', 'd', 'e'].map(letter => ({
symbol: Symbol(letter),
params: [],
}));
const type = callback(...typeVars);
return {
typeVars: occurring(type, new Set([a, b, c, d, e])),
typeVars: occurring(type, new Set(typeVars)),
type,
};
};
@ -34,60 +22,10 @@ export const makeGeneric = callback => {
// From the given set of type variables, return only those that occur in the given type.
export const occurring = (type, typeVars) => {
if (typeVars.has(type)) {
// type IS a type variable:
return new Set([type]);
}
if (type.in !== undefined) {
// function type
return new Set([
...occurring(type.in, typeVars),
...occurring(type.out, typeVars)]);
}
if (type.listOf !== undefined) {
return occurring(type.listOf, typeVars);
}
return new Set();
}
export const properUnify = eqDict => (
{typeVars: formalTypeVars, type: formalType},
{typeVars: actualTypeVars, type: actualType},
) => {
if (getEq(eqDict)(formalType)(actualType)) {
return {
substitutions: new Map(),
typeVars: new Set([
...actualTypeVars,
// ...formalTypeVars, // <- i don't think we need these?
]),
type: actualType,
}
}
if (formalTypeVars.has(formalType)) {
// formalType is type variable -> substitute it by actualType
return {
substitutions: new Map([[formalType, actualType]]),
typeVars: new Set([
...actualTypeVars,
...formalTypeVars,
].filter(a => a !== formalType)),
type: actualType,
}
}
if (actualTypeVars.has(actualType)) {
// same as above, but in opposite direction:
// actualType is type variable -> substitute it by formalType
return {
substitutions: new Map([[actualType, formalType]]),
typeVars: new Set([
...actualTypeVars,
...formalTypeVars,
].filter(a => a !== actualType)),
type: formalType,
}
}
// WIP...
return new Set(type.params.flatMap(p => [...occurring(p, typeVars)]));
}
const mergeOneWay = (m1, m2) => {
@ -103,7 +41,17 @@ const mergeOneWay = (m1, m2) => {
return [true, m1copy, m2copy, new Set()]; // stable
}
export const mergeSubstitutions = (m1, m2) => {
export const mergeTwoWay = (m1, m2) => {
// check for conflicts:
for (const [typeVar, actual] of m1) {
if (m2.has(typeVar)) {
const other = m2.get(typeVar);
if (!eqType(actual, other)) {
throw new Error(`conflicting substitution: ${pretty(actual)}vs. ${pretty(other)}`);
}
}
}
// actually merge
let stable = false;
let deletedTypeVars = new Set();
while (!stable) {
@ -115,150 +63,80 @@ export const mergeSubstitutions = (m1, m2) => {
return [new Map([...m1, ...m2]), deletedTypeVars];
}
// Currently very ad-hoc.
// 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 unify = (
{typeVars: formalTypeVars, type: formalType},
{typeVars: actualTypeVars, type: actualType},
) => {
if (deepEqual(formalType, actualType)) {
return {
substitutions: new Map(),
typeVars: new Set([
...actualTypeVars,
...formalTypeVars]),
type: actualType,
}
}
// console.log("unify", {formalTypeVars, formalType, actualTypeVars, actualType});
if (formalTypeVars.has(formalType)) {
// simplest case: substitute formal type param
// simplest case: formalType is a type paramater
// => substitute with actualType
return {
substitutions: new Map([[formalType, actualType]]),
typeVars: new Set([
...actualTypeVars,
...formalTypeVars].filter(a => a !== formalType)),
...[...formalTypeVars].filter(a => a !== formalType),
]),
type: actualType,
};
}
if (actualTypeVars.has(actualType)) {
// same as above, but in the other direction
return {
substitutions: new Map([[actualType, formalType]]),
typeVars: new Set([
...actualTypeVars,
...formalTypeVars].filter(a => a !== actualType)),
...[...actualTypeVars].filter(a => a !== actualType),
...formalTypeVars,
]),
type: formalType,
};
}
if (formalType.in !== undefined) {
// function type
if (actualType.in === undefined) {
throw new Error(`cannot assign ${pretty(actualType)} to ${pretty(formalType)}`);
// recursively unify
if (formalType.symbol !== actualType.symbol) {
throw new Error(`cannot unify ${pretty(formalType.symbol)} and ${pretty(actualType.symbol)}`);
}
else {
// both are function type
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)) {
if (!deepEqual(actual, outType.substitutions.get(typeVar))) {
throw new Error(`conflicting assignment for ${pretty(typeVar)}: ${pretty(a)}`);
}
}
}
// merge substitutions
const [newSubstitutions, deletedTypeVars] = mergeSubstitutions(
inType.substitutions, outType.substitutions);
// const newSubstitutions = new Map([
// ...inType.substitutions,
// ...outType.substitutions,
// ]);
const newTypeVars = new Set([
const unifiedParams = zip(formalType.params, actualType.params).map(([formalParam, actualParam]) => unify({typeVars: formalTypeVars, type: formalParam}, {typeVars: actualTypeVars, type: actualParam}));
const [unifiedSubstitusions, deleted] = unifiedParams.reduce(([substitutionsSoFar, deletedSoFar], cur) => {
// console.log('merging', substitutionsSoFar, cur.substitutions);
const [newSubstitutions, deleted] = mergeTwoWay(substitutionsSoFar, cur.substitutions);
return [newSubstitutions, deletedSoFar.union(deleted)];
}, [new Map(), new Set()]);
const unifiedTypeVars = new Set([
...actualTypeVars,
...formalTypeVars].filter(a => !newSubstitutions.has(a) && !deletedTypeVars.has(a)));
...formalTypeVars,
].filter(a => !unifiedSubstitusions.has(a) && !deleted.has(a)));
return {
substitutions: newSubstitutions,
typeVars: newTypeVars,
type: fnType({in: inType.type, out: outType.type}),
substitutions: unifiedSubstitusions,
typeVars: unifiedTypeVars,
type: {
symbol: formalType.symbol,
params: unifiedParams.map(p => p.type),
},
};
}
}
if (formalType.listOf !== undefined) {
// list type
if (actualType.listOf === undefined) {
throw new Error(`cannot assign ${pretty(actualType)} to ${pretty(formalType)}`);
}
else {
// both are list type
const elementType = unify(
{typeVars: formalTypeVars, type: formalType.listOf},
{typeVars: actualTypeVars, type: actualType.listOf});
return {
substitutions: elementType.substitutions,
typeVars: new Set([
...actualTypeVars,
...formalTypeVars].filter(a => !elementType.substitutions.has(a))),
type: lsType(elementType.type),
};
}
}
if (formalType.numDict !== undefined) {
if (actualType.numDict === undefined) {
throw new Error(`cannot assign ${pretty(actualType)} to ${pretty(formalType)}`);
}
else {
// both are NumDict type
const underlyingType = unify(
{typeVars: formalTypeVars, type: formalType.numDict},
{typeVars: actualTypeVars, type: actualType.numDict});
return {
substitutions: underlyingType.substitutions,
typeVars: new Set([
...actualTypeVars,
...formalTypeVars].filter(a => !underlyingType.substitutions.has(a))),
type: numDictType(underlyingType.type),
};
}
}
throw new Error("i don't know what to do :(");
};
// 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)) {
// type IS a type var to be substituted:
return substitutions.get(type);
}
if (type.listOf !== undefined) {
// list type
return lsType(substitute(type.listOf, substitutions));
}
if (type.in !== undefined) {
// function type
return fnType({
in: substitute(type.in, substitutions),
out: substitute(type.out, substitutions),
})
}
return type;
return {
symbol: type.symbol,
params: type.params.map(p => substitute(p, substitutions)),
};
}
export const assign = (genFnType, paramType) => {
const [inType, outType] = genFnType.type.params;
const matchedInType = unify({
typeVars: genFnType.typeVars,
type: genFnType.type.in,
type: inType,
}, paramType);
const substitutedOutType = substitute(genFnType.type.out, matchedInType.substitutions);
const substitutedOutType = substitute(outType, matchedInType.substitutions);
return {
typeVars: matchedInType.typeVars,
type: substitutedOutType,

View file

@ -1,21 +1,22 @@
import { Bool, Int } from "../primitives/symbols.js";
import { lsType } from "../structures/list_common.js";
import { fnType } from "../metacircular.js";
import { assign, makeGeneric, mergeSubstitutions, unify } from "./generics.js";
import { lsType } from "../structures/list.js";
import { fnType } from "../structures/function.js";
import { assign, makeGeneric, unify } from "./generics.js";
import { pretty } from "../util.js";
// a -> Int
const a_to_Int = makeGeneric(a => fnType({in: a, out: Int}));
// Bool -> Int
const Bool_to_Int = makeGeneric(() => fnType({in: lsType(Bool), out: Int}));
console.log("should be: Bool -> Int")
console.log(unify(a_to_Int, Bool_to_Int));
console.log("should be: [Bool] -> Int")
console.log(pretty(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}));
console.log("should be: (Bool -> Bool) -> a");
console.log(unify(fnType2, fnType3));
console.log(pretty(unify(fnType2, fnType3)));
// (a -> b) -> [a] -> [b]
const mapFnType = makeGeneric((a,b) =>
@ -27,7 +28,7 @@ const mapFnType = makeGeneric((a,b) =>
const idFnType = makeGeneric(a =>
fnType({in: a, out: a}));
console.log("should be: [a] -> [a]");
console.log(assign(mapFnType, idFnType));
console.log(pretty(assign(mapFnType, idFnType)));
// (a -> Int) -> [a] -> a
const weirdFnType = makeGeneric(a =>
@ -36,4 +37,4 @@ const weirdFnType = makeGeneric(a =>
out: fnType({in: lsType(a), out: a}),
}));
console.log("should be: [Int] -> Int");
console.log(assign(weirdFnType, idFnType));
console.log(pretty(assign(weirdFnType, idFnType)));

View file

@ -1,2 +0,0 @@

View file

@ -1,39 +1,39 @@
import { fnType } from "../metacircular.js";
import {Function} from "../metacircular.js";
// import { fnType } from "../metacircular.js";
// import {Function} from "../metacircular.js";
// import {Typed} from "../typed.js";
// // import {Typed} from "../typed.js";
// import {Bool} from "../primitives/symbols.js";
// import {deepEqual} from "../util.js";
// import {Conformable, conformanceCheck} from "../typeclasses/conformable.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');
// // // 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))
// }
// // 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}),
// // 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},
// ];
// // {i: idBoundToType, t: Conformable},
// // {i: idIsConform, t: conformanceCheck},
// // ];
// generates explicitly typed id-function
export const makeIdFn = typ => {
const Typ_to_Typ = fnType({in: typ, out: typ});
const id = x => x;
return {l:[
{i: id , t: Typ_to_Typ},
{i: Typ_to_Typ, t: Function},
]};
};
// // generates explicitly typed id-function
// export const makeIdFn = typ => {
// const Typ_to_Typ = fnType({in: typ, out: typ});
// const id = x => x;
// return {l:[
// {i: id , t: Typ_to_Typ},
// {i: Typ_to_Typ, t: Function},
// ]};
// };

View file

@ -1,8 +1,8 @@
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 "../metacircular.js";
// import { 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 "../metacircular.js";
// import {Num, NumDict, getType, getMul} from "../typeclasses/num.js";
@ -27,24 +27,24 @@ import { fnType } from "../metacircular.js";
// ];
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 = fnType({in: numType, out: numType});
return {l:[
{i: square , t: squareFunction},
{i: squareFunction, t: Function},
]};
};
// 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 = fnType({in: numType, out: numType});
// return {l:[
// {i: square , t: squareFunction},
// {i: squareFunction, t: Function},
// ]};
// };
const makeSquareType = fnType({in: Typed, out: Module});
// const makeSquareType = fnType({in: Typed, out: Module});
export const ModuleSquare = {l:[
{i: makeSquare , t: makeSquareType},
{i: makeSquareType, t: Function},
]};
// export const ModuleSquare = {l:[
// {i: makeSquare , t: makeSquareType},
// {i: makeSquareType, t: Function},
// ]};

View file

@ -1,20 +1,16 @@
import { DefaultMap } from "./util.js";
import { Bool } from "./primitives/symbols.js";
import { typedFnType } from "./structures/function.js";
import { deepEqual } 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 })));
// TODO: 'Type' (and its instances) are itself instances of (String,[Type]) (=the product type of String and list of Type)
// so is 'Type' just an alias for (String, [Type])
export const Type = { symbol: Symbol('Type'), params: [] };
// type constructor for function types
export const fnType = ({ in: inType, out: outType }) => fnTypeRegistry.getdefault(inType, true).getdefault(outType, true);
export const getSymbol = type => type.symbol;
export const getParams = type => type.params;
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;
// we can test whether types are equal:
export const eqType = deepEqual;
// a module is just a set of typed objects
// each 'typed object' is implicitly an instance of TypeLink (defined below)
@ -28,38 +24,14 @@ export const ModuleMetaCircular = {l:[
// Type : Type
{i: Type, t: Type},
// Function : Type
{i: Function, t: Type},
// ...typedFnType(getSymbol, fnType => fnType({in: Type, out: Int})),
// (Function -> Type) : Function
{i: fnType({in: Function, out: Type}), t: Function},
// ...typedFnType(getParams, fnType => fnType({in: Type, out: lsType(Type)})),
{i: getIn , t: fnType({in: Function, out: Type})},
{i: getOut, t: fnType({in: Function, out: Type})},
...typedFnType(eqType, fnType => fnType({
in: Type,
out: fnType({
in: Type,
out: Bool,
})})),
]};
// 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 })),
];
};

View file

@ -1,5 +1,5 @@
import { fnType } from "../metacircular.js";
import {Type, Function} from "../metacircular.js";
import { fnType } from "../structures/function.js";
import { Type } from "../metacircular.js";
import { Bool } from "./symbols.js";
const eqBool = x => y => x === y;

View file

@ -1,5 +1,5 @@
import { fnType } from "../type_registry.js";
import {Type, Function} from "../metacircular.js";
import { fnType } from "../structures/function.js";
import { Type } from "../metacircular.js";
import {Byte, Bool} from "./symbols.js";
const eqByte = x => y => x === y;

View file

@ -1,4 +1,4 @@
import { typedFnType } from "../type_registry.js";
import { typedFnType } from "../structures/function.js";
import { Type } from "../metacircular.js";
import {Char, Bool} from "./symbols.js";

View file

@ -1,6 +1,5 @@
import { fnType } from "../metacircular.js";
import {Type, Function} from "../metacircular.js";
import { fnType } from "../structures/function.js";
import { Type } from "../metacircular.js";
import {Bool, Double} from "./symbols.js";
export const addDouble = x => y => x + y;

View file

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

View file

@ -1,7 +1,7 @@
// to break up dependency cycles, symbols of primitive types have their own JS module
// to break up dependency cycles, primitive types are defined in their own JS module
export const Bool = Symbol('Bool');
export const Int = Symbol('Int');
export const Double = Symbol('Double');
export const Byte = Symbol('Byte');
export const Char = Symbol('Char');
export const Int = { symbol: Symbol('Int') , params: [] };
export const Bool = { symbol: Symbol('Bool') , params: [] };
export const Double = { symbol: Symbol('Double'), params: [] };
export const Byte = { symbol: Symbol('Byte') , params: [] };
export const Char = { symbol: Symbol('Char') , params: [] };

View file

@ -58,8 +58,15 @@ wip:
The sad(?) part about all of this, is that I'm converging with Haskell/Lean.
- treat all values as polymorphic? (non-polymorphic values simply have empty set of type variables)
todo:
- rename Type to a NominalType?
const nominalType = (name, params) => ({left: name, right: params});
type of every nominal type is (String, [Type])
- what about type links: they connect anything to its type... what is the type of 'anything'?
- treat all values as polymorphic? (non-polymorphic values simply have empty set of type variables)
- type inferencing can be reduced to finding a graph isomorphism?

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))
)),
// 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)
)
)),
]};
};
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})},
]};
};

View file

@ -1,5 +1,6 @@
import { makeGeneric } from "../generics/generics";
import { Type, typedFnType } from "../metacircular";
import { Type } from "../metacircular";
import { typedFnType } from "../structures/function";
import { Bool, Byte, Char, Double, Int } from "../primitives/symbols";
import { deepEqual } from "../util";
import { eqDictType } from "./eq_type";

View file

@ -1,7 +1,8 @@
import { makeGeneric } from "../generics/generics.js";
import { addDouble, mulDouble } from "../primitives/double.js";
import { addInt, mulInt } from "../primitives/int.js";
import { Type, typedFnType, typedFnType2 } from "../metacircular.js";
import { Type } from "../metacircular.js";
import { typedFnType, typedFnType2 } from "../structures/function.js";
import { Double, Int } from "../primitives/symbols.js";
import { numDictType } from "./num_type.js";

View file

@ -1,6 +1,6 @@
import { assign } from "../generics/generics.js";
import { makeGeneric } from "../generics/generics.js";
import { fnType } from "../metacircular.js";
import { fnType } from "../structures/function.js";
import { Double, Int } from "../primitives/symbols.js";
import { getMul, NumInstances } from "./num.js";
import { numDictType } from "./num_type.js";

View file

@ -1,17 +1,21 @@
import { fnType } from "./metacircular.js";
import {Type, Function} from "./metacircular.js";
import { fnType } from "./structures/function.js";
import { Type } from "./metacircular.js";
export const Typed = Symbol('Typed');
// Everything is (implicitly) typed by the Any type.
export const Any = { symbol: Symbol('Any'), params: [] };
// A type-link, connecting a value to its Type.
export const Typed = { symbol: Symbol('Typed'), params: [] };
const getInst = lnk => lnk.i;
const getType = lnk => lnk.t;
const Typed_to_Type = fnType({in: Typed, out: Type});
const Typed_to_Type = fnType({in: Any, out: Type});
export const ModuleTyped = {l:[
{i: Typed, t: Type},
{i: Typed_to_Type, t: Function},
{i: Typed_to_Type, t: Type},
{i: getInst, t: Typed_to_Type},
{i: getType, t: Typed_to_Type},

View file

@ -22,6 +22,11 @@ export function deepEqual(a, b) {
return true;
}
// zip two arrays
export function zip(a, b) {
return a.map((k, i) => [k, b[i]]);
}
export class DefaultMap {
constructor(defaultValue, ...rest) {
this.defaultValue = defaultValue;