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 { eqType } from "../metacircular.js";
import { fnType } from "../metacircular.js"; import { pretty, zip } from "../util.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);
// constructor for generic types // constructor for generic types
// for instance, the type: // 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})})) // makeGeneric(a => fnType({in: a, out: fnType({in: a, out: Bool})}))
export const makeGeneric = callback => { export const makeGeneric = callback => {
// type variables to make available: // type variables to make available:
const a = Symbol('a'); const typeVars = ['a', 'b', 'c', 'd', 'e'].map(letter => ({
const b = Symbol('b'); symbol: Symbol(letter),
const c = Symbol('c'); params: [],
const d = Symbol('d'); }));
const e = Symbol('e'); const type = callback(...typeVars);
const type = callback(a, b, c, d, e);
return { return {
typeVars: occurring(type, new Set([a, b, c, d, e])), typeVars: occurring(type, new Set(typeVars)),
type, 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. // From the given set of type variables, return only those that occur in the given type.
export const occurring = (type, typeVars) => { export const occurring = (type, typeVars) => {
if (typeVars.has(type)) { if (typeVars.has(type)) {
// type IS a type variable:
return new Set([type]); return new Set([type]);
} }
if (type.in !== undefined) { return new Set(type.params.flatMap(p => [...occurring(p, typeVars)]));
// 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...
} }
const mergeOneWay = (m1, m2) => { const mergeOneWay = (m1, m2) => {
@ -103,7 +41,17 @@ const mergeOneWay = (m1, m2) => {
return [true, m1copy, m2copy, new Set()]; // stable 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 stable = false;
let deletedTypeVars = new Set(); let deletedTypeVars = new Set();
while (!stable) { while (!stable) {
@ -115,150 +63,80 @@ export const mergeSubstitutions = (m1, m2) => {
return [new Map([...m1, ...m2]), deletedTypeVars]; 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): // 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 // https://www.dai.ed.ac.uk/groups/ssp/bookpages/quickprolog/node12.html
export const unify = ( export const unify = (
{typeVars: formalTypeVars, type: formalType}, {typeVars: formalTypeVars, type: formalType},
{typeVars: actualTypeVars, type: actualType}, {typeVars: actualTypeVars, type: actualType},
) => { ) => {
if (deepEqual(formalType, actualType)) { // console.log("unify", {formalTypeVars, formalType, actualTypeVars, actualType});
return {
substitutions: new Map(),
typeVars: new Set([
...actualTypeVars,
...formalTypeVars]),
type: actualType,
}
}
if (formalTypeVars.has(formalType)) { if (formalTypeVars.has(formalType)) {
// simplest case: substitute formal type param // simplest case: formalType is a type paramater
// => substitute with actualType
return { return {
substitutions: new Map([[formalType, actualType]]), substitutions: new Map([[formalType, actualType]]),
typeVars: new Set([ typeVars: new Set([
...actualTypeVars, ...actualTypeVars,
...formalTypeVars].filter(a => a !== formalType)), ...[...formalTypeVars].filter(a => a !== formalType),
]),
type: actualType, type: actualType,
}; };
} }
if (actualTypeVars.has(actualType)) { if (actualTypeVars.has(actualType)) {
// same as above, but in the other direction // same as above, but in the other direction
return { return {
substitutions: new Map([[actualType, formalType]]), substitutions: new Map([[actualType, formalType]]),
typeVars: new Set([ typeVars: new Set([
...actualTypeVars, ...[...actualTypeVars].filter(a => a !== actualType),
...formalTypeVars].filter(a => a !== actualType)), ...formalTypeVars,
]),
type: formalType, type: formalType,
}; };
} }
// recursively unify
if (formalType.in !== undefined) { if (formalType.symbol !== actualType.symbol) {
// function type throw new Error(`cannot unify ${pretty(formalType.symbol)} and ${pretty(actualType.symbol)}`);
if (actualType.in === undefined) {
throw new Error(`cannot assign ${pretty(actualType)} to ${pretty(formalType)}`);
}
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([
...actualTypeVars,
...formalTypeVars].filter(a => !newSubstitutions.has(a) && !deletedTypeVars.has(a)));
return {
substitutions: newSubstitutions,
typeVars: newTypeVars,
type: fnType({in: inType.type, out: outType.type}),
};
}
} }
else {
if (formalType.listOf !== undefined) { const unifiedParams = zip(formalType.params, actualType.params).map(([formalParam, actualParam]) => unify({typeVars: formalTypeVars, type: formalParam}, {typeVars: actualTypeVars, type: actualParam}));
// list type const [unifiedSubstitusions, deleted] = unifiedParams.reduce(([substitutionsSoFar, deletedSoFar], cur) => {
if (actualType.listOf === undefined) { // console.log('merging', substitutionsSoFar, cur.substitutions);
throw new Error(`cannot assign ${pretty(actualType)} to ${pretty(formalType)}`); const [newSubstitutions, deleted] = mergeTwoWay(substitutionsSoFar, cur.substitutions);
} return [newSubstitutions, deletedSoFar.union(deleted)];
else { }, [new Map(), new Set()]);
// both are list type const unifiedTypeVars = new Set([
const elementType = unify( ...actualTypeVars,
{typeVars: formalTypeVars, type: formalType.listOf}, ...formalTypeVars,
{typeVars: actualTypeVars, type: actualType.listOf}); ].filter(a => !unifiedSubstitusions.has(a) && !deleted.has(a)));
return { return {
substitutions: elementType.substitutions, substitutions: unifiedSubstitusions,
typeVars: new Set([ typeVars: unifiedTypeVars,
...actualTypeVars, type: {
...formalTypeVars].filter(a => !elementType.substitutions.has(a))), symbol: formalType.symbol,
type: lsType(elementType.type), params: unifiedParams.map(p => p.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) => { export const substitute = (type, substitutions) => {
if (substitutions.has(type)) { if (substitutions.has(type)) {
// type IS a type var to be substituted:
return substitutions.get(type); return substitutions.get(type);
} }
if (type.listOf !== undefined) { return {
// list type symbol: type.symbol,
return lsType(substitute(type.listOf, substitutions)); params: type.params.map(p => substitute(p, substitutions)),
} };
if (type.in !== undefined) {
// function type
return fnType({
in: substitute(type.in, substitutions),
out: substitute(type.out, substitutions),
})
}
return type;
} }
export const assign = (genFnType, paramType) => { export const assign = (genFnType, paramType) => {
const [inType, outType] = genFnType.type.params;
const matchedInType = unify({ const matchedInType = unify({
typeVars: genFnType.typeVars, typeVars: genFnType.typeVars,
type: genFnType.type.in, type: inType,
}, paramType); }, paramType);
const substitutedOutType = substitute(genFnType.type.out, matchedInType.substitutions); const substitutedOutType = substitute(outType, matchedInType.substitutions);
return { return {
typeVars: matchedInType.typeVars, typeVars: matchedInType.typeVars,
type: substitutedOutType, type: substitutedOutType,

View file

@ -1,21 +1,22 @@
import { Bool, Int } from "../primitives/symbols.js"; import { Bool, Int } from "../primitives/symbols.js";
import { lsType } from "../structures/list_common.js"; import { lsType } from "../structures/list.js";
import { fnType } from "../metacircular.js"; import { fnType } from "../structures/function.js";
import { assign, makeGeneric, mergeSubstitutions, unify } from "./generics.js"; import { assign, makeGeneric, unify } from "./generics.js";
import { pretty } from "../util.js";
// a -> Int // a -> Int
const a_to_Int = makeGeneric(a => fnType({in: a, out: Int})); const a_to_Int = makeGeneric(a => fnType({in: a, out: Int}));
// Bool -> Int // Bool -> Int
const Bool_to_Int = makeGeneric(() => fnType({in: lsType(Bool), out: Int})); const Bool_to_Int = makeGeneric(() => fnType({in: lsType(Bool), out: Int}));
console.log("should be: Bool -> Int") console.log("should be: [Bool] -> Int")
console.log(unify(a_to_Int, Bool_to_Int)); console.log(pretty(unify(a_to_Int, Bool_to_Int)));
// (a -> a) -> b // (a -> a) -> b
const fnType2 = makeGeneric((a,b) => fnType({in: fnType({in: a, out: a}), out: b})); const fnType2 = makeGeneric((a,b) => fnType({in: fnType({in: a, out: a}), out: b}));
// (Bool -> Bool) -> a // (Bool -> Bool) -> a
const fnType3 = makeGeneric(a => fnType({in: fnType({in: Bool, out: Bool}), out: a})); const fnType3 = makeGeneric(a => fnType({in: fnType({in: Bool, out: Bool}), out: a}));
console.log("should be: (Bool -> Bool) -> a"); console.log("should be: (Bool -> Bool) -> a");
console.log(unify(fnType2, fnType3)); console.log(pretty(unify(fnType2, fnType3)));
// (a -> b) -> [a] -> [b] // (a -> b) -> [a] -> [b]
const mapFnType = makeGeneric((a,b) => const mapFnType = makeGeneric((a,b) =>
@ -27,7 +28,7 @@ const mapFnType = makeGeneric((a,b) =>
const idFnType = makeGeneric(a => const idFnType = makeGeneric(a =>
fnType({in: a, out: a})); fnType({in: a, out: a}));
console.log("should be: [a] -> [a]"); console.log("should be: [a] -> [a]");
console.log(assign(mapFnType, idFnType)); console.log(pretty(assign(mapFnType, idFnType)));
// (a -> Int) -> [a] -> a // (a -> Int) -> [a] -> a
const weirdFnType = makeGeneric(a => const weirdFnType = makeGeneric(a =>
@ -36,4 +37,4 @@ const weirdFnType = makeGeneric(a =>
out: fnType({in: lsType(a), out: a}), out: fnType({in: lsType(a), out: a}),
})); }));
console.log("should be: [Int] -> Int"); 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 { fnType } from "../metacircular.js";
import {Function} 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 {Bool} from "../primitives/symbols.js";
// import {deepEqual} from "../util.js"; // // import {deepEqual} from "../util.js";
// import {Conformable, conformanceCheck} from "../typeclasses/conformable.js"; // // import {Conformable, conformanceCheck} from "../typeclasses/conformable.js";
// // Identity function // // // Identity function
// const idBoundToType = Symbol('idBoundToType'); // // const idBoundToType = Symbol('idBoundToType');
// const id = type => x => x; // // const id = type => x => x;
// const idFn = {name: "id", inType: Type, outType: {inType: Type, outType: Type}, fn: id}; // // const idFn = {name: "id", inType: Type, outType: {inType: Type, outType: Type}, fn: id};
// const idIsConform = { // // const idIsConform = {
// name: "isConform", // // name: "isConform",
// inType: idBoundToType, // // inType: idBoundToType,
// outType: Bool, // // outType: Bool,
// fn: fn => deepEqual(fnIn(fn), fnOut(fn)) // // fn: fn => deepEqual(fnIn(fn), fnOut(fn))
// } // // }
// export const ModuleId = [ // // export const ModuleId = [
// {i: idBoundToType, t: Type}, // // {i: idBoundToType, t: Type},
// {i: idFn, t: Function}, // // {i: idFn, t: Function},
// ... makeTypedFnInOut({f: idBoundToType, inType: Type, outType: Type}), // // ... makeTypedFnInOut({f: idBoundToType, inType: Type, outType: Type}),
// {i: idBoundToType, t: Conformable}, // // {i: idBoundToType, t: Conformable},
// {i: idIsConform, t: conformanceCheck}, // // {i: idIsConform, t: conformanceCheck},
// ]; // // ];
// generates explicitly typed id-function // // generates explicitly typed id-function
export const makeIdFn = typ => { // export const makeIdFn = typ => {
const Typ_to_Typ = fnType({in: typ, out: typ}); // const Typ_to_Typ = fnType({in: typ, out: typ});
const id = x => x; // const id = x => x;
return {l:[ // return {l:[
{i: id , t: Typ_to_Typ}, // {i: id , t: Typ_to_Typ},
{i: Typ_to_Typ, t: Function}, // {i: Typ_to_Typ, t: Function},
]}; // ]};
}; // };

View file

@ -1,8 +1,8 @@
import {Function, getIn, getOut} from "../metacircular.js"; // import { getIn, getOut } from "../metacircular.js";
import {Module} from "../structures/list_types/module.js"; // import {Module} from "../structures/list_types/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 "../metacircular.js"; // import { fnType } from "../metacircular.js";
// import {Num, NumDict, getType, getMul} from "../typeclasses/num.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}) => { // export const makeSquare = ({i: mul, t: mulFunction}) => {
const numType = getIn(mulFunction); // const numType = getIn(mulFunction);
const boundMulFunction = getOut(mulFunction); // const boundMulFunction = getOut(mulFunction);
if (!deepEqual(getOut(boundMulFunction), numType) || !deepEqual(getIn(boundMulFunction), numType)) { // if (!deepEqual(getOut(boundMulFunction), numType) || !deepEqual(getIn(boundMulFunction), numType)) {
console.log(getOut(boundMulFunction), getIn(boundMulFunction), numType); // console.log(getOut(boundMulFunction), getIn(boundMulFunction), numType);
throw new Error("invalid signature"); // throw new Error("invalid signature");
} // }
const square = x => mul(x)(x); // const square = x => mul(x)(x);
const squareFunction = fnType({in: numType, out: numType}); // const squareFunction = fnType({in: numType, out: numType});
return {l:[ // return {l:[
{i: square , t: squareFunction}, // {i: square , t: squareFunction},
{i: squareFunction, t: Function}, // {i: squareFunction, t: Function},
]}; // ]};
}; // };
const makeSquareType = fnType({in: Typed, out: Module}); // const makeSquareType = fnType({in: Typed, out: Module});
export const ModuleSquare = {l:[ // export const ModuleSquare = {l:[
{i: makeSquare , t: makeSquareType}, // {i: makeSquare , t: makeSquareType},
{i: makeSquareType, t: Function}, // {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. // TODO: 'Type' (and its instances) are itself instances of (String,[Type]) (=the product type of String and list of Type)
// It is a cheap workaround for JS lacking customizable hash-functions and equality-testing-functions. // so is 'Type' just an alias for (String, [Type])
// This same pattern is repeated throughout the code for all non-nullary type constructors (list, sum, product, ...) export const Type = { symbol: Symbol('Type'), params: [] };
const fnTypeRegistry = new DefaultMap(inType => new DefaultMap(outType => ({ in: inType, out: outType })));
// type constructor for function types export const getSymbol = type => type.symbol;
export const fnType = ({ in: inType, out: outType }) => fnTypeRegistry.getdefault(inType, true).getdefault(outType, true); export const getParams = type => type.params;
export const Type = Symbol('Type'); // we can test whether types are equal:
export const Function = Symbol('Function'); export const eqType = deepEqual;
// 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 // a module is just a set of typed objects
// each 'typed object' is implicitly an instance of TypeLink (defined below) // each 'typed object' is implicitly an instance of TypeLink (defined below)
@ -28,38 +24,14 @@ export const ModuleMetaCircular = {l:[
// Type : Type // Type : Type
{i: Type, t: Type}, {i: Type, t: Type},
// Function : Type // ...typedFnType(getSymbol, fnType => fnType({in: Type, out: Int})),
{i: Function, t: Type},
// (Function -> Type) : Function // ...typedFnType(getParams, fnType => fnType({in: Type, out: lsType(Type)})),
{i: fnType({in: Function, out: Type}), t: Function},
{i: getIn , t: fnType({in: Function, out: Type})}, ...typedFnType(eqType, fnType => fnType({
{i: getOut, t: fnType({in: Function, out: Type})}, 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,6 +1,6 @@
import { fnType } from "../metacircular.js"; import { fnType } from "../structures/function.js";
import {Type, Function} from "../metacircular.js"; import { Type } from "../metacircular.js";
import {Bool} from "./symbols.js"; import { Bool } from "./symbols.js";
const eqBool = x => y => x === y; const eqBool = x => y => x === y;

View file

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

View file

@ -1,5 +1,5 @@
import { typedFnType } from "../type_registry.js"; import { typedFnType } from "../structures/function.js";
import {Type} from "../metacircular.js"; import { Type } from "../metacircular.js";
import {Char, Bool} from "./symbols.js"; import {Char, Bool} from "./symbols.js";
const eq = x => y => x === y; const eq = x => y => x === y;

View file

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

View file

@ -1,5 +1,5 @@
import { fnType } from "../metacircular.js"; import { fnType } from "../structures/function.js";
import {Type, Function} from "../metacircular.js"; import { Type } from "../metacircular.js";
import {Bool, Int} from "./symbols.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: Symbol('Int') , params: [] };
export const Int = Symbol('Int'); export const Bool = { symbol: Symbol('Bool') , params: [] };
export const Double = Symbol('Double'); export const Double = { symbol: Symbol('Double'), params: [] };
export const Byte = Symbol('Byte'); export const Byte = { symbol: Symbol('Byte') , params: [] };
export const Char = Symbol('Char'); 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. 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: 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? - 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 { typedFnType } from "./function.js";
import { fnType } from "../metacircular.js"; import { Type } from "../metacircular.js";
import {Type, Function} from "../metacircular.js"; import { Int } from "../primitives/symbols.js";
import { makeListModule } from "./list_common.js"; import { DefaultMap } from "../util.js";
import { Module } from "./list_types/module.js"; import { makeGeneric } from "../generics/generics.js";
const Type_to_Type = fnType({in: Type, out: Type}); const symbolList = Symbol('List');
const Type_to_Module = fnType({in: Type, out: Module});
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:[ export const ModuleList = {l:[
{i: lsType , t: Type_to_Type}, // Type -> Type
{i: Type_to_Type , t: Function}, ...typedFnType(lsType, fnType => fnType
/* in */ (Type)
/* out */ (Type)
),
{i: makeListModule, t: Type_to_Module}, // [a]
{i: Type_to_Module, t: Function}, {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 { 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 Module = lsType(Typed); // a Module is a list of Typeds
export const ModuleModule = makeListModule(Typed); // the module containing operations on Module export const ModuleModule = makeListModule(Typed); // the module containing operations on Module

View file

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

View file

@ -1,37 +1,51 @@
import { fnType } from "../metacircular.js"; import { makeGeneric } from "../generics/generics.js";
import { Function, Type } from "../metacircular.js"; import { Type } from "../metacircular.js";
import { DefaultMap } from "../util.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 // 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: // In JS, all products are encoded in the same way:
const constructor = left => right => ({left, right}); const constructor = left => right => ({left, right});
const getLeft = product => product.left; const getLeft = product => product.left;
const getRight = product => product.right; const getRight = product => product.right;
// Given two types A and B, create the type (A × B). export const ModuleProduct = {l: [
export const makeProductType = (leftType, rightType) => { // binary type constructor
const pType = prodType(leftType, rightType); // Type -> Type -> Type
...typedFnType(prodType, fnType => fnType
(Type)
(fnType
(Type)
(Type)
)
),
const leftFnType = fnType({in: pType, out: leftType}); // a -> b -> (a, b)
const rightFnType = fnType({in: pType, out: rightType}); ...typedFnType(constructor, fnType => makeGeneric((a, b) => fnType
(a)
(fnType
(b)
(prodType(a)(b))
)
)),
const constructorBoundType = fnType({in: rightType, out: pType}); // (a, b) -> a
const constructorType = fnType({in: leftType , out: constructorBoundType}); ...typedFnType(getLeft, fnType => makeGeneric((a, b) => fnType
(prodType(a)(b))
(a)
)),
return {l:[ // (a, b) -> b
{i: pType, t: Type}, ...typedFnType(getRight, fnType => makeGeneric((a, b) => fnType
(prodType(a)(b))
{i: getLeft , t: leftFnType}, (b)
{i: getRight , t: rightFnType}, )),
{i: leftFnType , t: Function}, ]};
{i: rightFnType, t: Function},
{i: constructor , t: constructorType},
{i: constructorType , t: Function},
{i: constructorBoundType, t: Function},
]};
};

View file

@ -1,53 +1,60 @@
import { prodType } from "./product.js"; import { prodType } from "./product.js";
import { fnType } from "../metacircular.js"; import { Type } from "../metacircular.js";
import { Function, Type } from "../metacircular.js";
import { Module } from "./list_types/module.js";
import { DefaultMap } from "../util.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 // 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 constructorLeft = left => ({variant: "L", value: left });
const constructorRight = right => ({variant: "R", value: right}); const constructorRight = right => ({variant: "R", value: right});
// (<uuid>, product(double, double)): product(int, type)
// signature: // signature:
// sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType // sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType
const match = sum => handlers => sum.variant === "L" const match = sum => handlers => sum.variant === "L"
? handlers.left(sum.value) ? handlers.left(sum.value)
: handlers.right(sum.value); : handlers.right(sum.value);
// Given two types A and B, create the type (A + B). export const ModuleSum = {l:[
export const makeSumType = (leftType, rightType) => { // binary type constructor
const sType = sumType(leftType, rightType); // Type -> Type -> Type
...typedFnType(sumType, fnType => fnType
(Type)
(fnType
(Type)
(Type)
),
),
const constructorLeftType = fnType({in: leftType , out: sType}); // a -> a | b
const constructorRightType = fnType({in: rightType, out: sType}); ...typedFnType(constructorLeft, fnType => makeGeneric((a, b) => fnType
(a)
(sumType(a)(b))
)),
// For each possible return type, the match function has a different signature. // b -> a | b
// We thus define a function that creates a properly typed match function. ...typedFnType(constructorRight, fnType => makeGeneric((a, b) => fnType
const makeMatchFn = returnType => { (b)
const handlersType = prodType( (sumType(a)(b))
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},
]};
};
return {l:[ // a | b -> (a -> c, b-> c) -> c
{i: sType , t: Type}, ...typedFnType(match, fnType => makeGeneric((a, b, c) => fnType
{i: constructorLeft , t: constructorLeftType}, (sumType(a)(b))
{i: constructorRight , t: constructorRightType}, (fnType
{i: constructorLeftType , t: Function}, (prodType
{i: constructorRightType, t: Function}, (fnType(a)(c))
(fnType(b)(c))
{i: makeMatchFn, t: fnType({in: Type, out: Module})}, )
]}; (c)
}; )
)),
]};

View file

@ -1,5 +1,6 @@
import { makeGeneric } from "../generics/generics"; 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 { Bool, Byte, Char, Double, Int } from "../primitives/symbols";
import { deepEqual } from "../util"; import { deepEqual } from "../util";
import { eqDictType } from "./eq_type"; import { eqDictType } from "./eq_type";

View file

@ -1,7 +1,8 @@
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
import { addDouble, mulDouble } from "../primitives/double.js"; import { addDouble, mulDouble } from "../primitives/double.js";
import { addInt, mulInt } from "../primitives/int.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 { Double, Int } from "../primitives/symbols.js";
import { numDictType } from "./num_type.js"; import { numDictType } from "./num_type.js";

View file

@ -1,6 +1,6 @@
import { assign } from "../generics/generics.js"; import { assign } from "../generics/generics.js";
import { makeGeneric } 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 { Double, Int } from "../primitives/symbols.js";
import { getMul, NumInstances } from "./num.js"; import { getMul, NumInstances } from "./num.js";
import { numDictType } from "./num_type.js"; import { numDictType } from "./num_type.js";

View file

@ -1,17 +1,21 @@
import { fnType } from "./metacircular.js"; import { fnType } from "./structures/function.js";
import {Type, Function} from "./metacircular.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 getInst = lnk => lnk.i;
const getType = lnk => lnk.t; 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:[ export const ModuleTyped = {l:[
{i: Typed, t: Type}, {i: Typed, t: Type},
{i: Typed_to_Type, t: Function}, {i: Typed_to_Type, t: Type},
{i: getInst, t: Typed_to_Type}, {i: getInst, t: Typed_to_Type},
{i: getType, t: Typed_to_Type}, {i: getType, t: Typed_to_Type},

View file

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