wip
This commit is contained in:
parent
afd78c3b3e
commit
29d20b2273
25 changed files with 369 additions and 469 deletions
|
|
@ -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 {
|
else {
|
||||||
// both are function type
|
const unifiedParams = zip(formalType.params, actualType.params).map(([formalParam, actualParam]) => unify({typeVars: formalTypeVars, type: formalParam}, {typeVars: actualTypeVars, type: actualParam}));
|
||||||
const inType = unify({typeVars: formalTypeVars, type: formalType.in}, {typeVars: actualTypeVars, type: actualType.in});
|
const [unifiedSubstitusions, deleted] = unifiedParams.reduce(([substitutionsSoFar, deletedSoFar], cur) => {
|
||||||
const outType = unify({typeVars: formalTypeVars, type: formalType.out}, {typeVars: actualTypeVars, type: actualType.out});
|
// console.log('merging', substitutionsSoFar, cur.substitutions);
|
||||||
// check for conflicts between 'in' and 'out' subsitutions
|
const [newSubstitutions, deleted] = mergeTwoWay(substitutionsSoFar, cur.substitutions);
|
||||||
for (const [typeVar, actual] of inType.substitutions) {
|
return [newSubstitutions, deletedSoFar.union(deleted)];
|
||||||
if (outType.substitutions.has(typeVar)) {
|
}, [new Map(), new Set()]);
|
||||||
if (!deepEqual(actual, outType.substitutions.get(typeVar))) {
|
const unifiedTypeVars = new Set([
|
||||||
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,
|
...actualTypeVars,
|
||||||
...formalTypeVars].filter(a => !newSubstitutions.has(a) && !deletedTypeVars.has(a)));
|
...formalTypeVars,
|
||||||
|
].filter(a => !unifiedSubstitusions.has(a) && !deleted.has(a)));
|
||||||
return {
|
return {
|
||||||
substitutions: newSubstitutions,
|
substitutions: unifiedSubstitusions,
|
||||||
typeVars: newTypeVars,
|
typeVars: unifiedTypeVars,
|
||||||
type: fnType({in: inType.type, out: outType.type}),
|
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) => {
|
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,
|
||||||
|
|
|
||||||
|
|
@ -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)));
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
64
lib/id.js
64
lib/id.js
|
|
@ -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},
|
||||||
]};
|
// ]};
|
||||||
};
|
// };
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
]};
|
// ]};
|
||||||
|
|
|
||||||
|
|
@ -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 })),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
@ -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 } from "./symbols.js";
|
import { Bool } from "./symbols.js";
|
||||||
|
|
||||||
const eqBool = x => y => x === y;
|
const eqBool = x => y => x === y;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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: [] };
|
||||||
|
|
|
||||||
|
|
@ -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
51
structures/function.js
Normal 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)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]};
|
||||||
|
|
@ -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))
|
||||||
|
)
|
||||||
|
))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -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},
|
|
||||||
]};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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},
|
|
||||||
]};
|
]};
|
||||||
};
|
|
||||||
|
|
@ -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
|
|
||||||
);
|
// a | b -> (a -> c, b-> c) -> c
|
||||||
const matchFnType = fnType({in: sType, out: fnType({in: handlersType, out: returnType})});
|
...typedFnType(match, fnType => makeGeneric((a, b, c) => fnType
|
||||||
return {l:[
|
(sumType(a)(b))
|
||||||
{i: match , t: matchFnType},
|
(fnType
|
||||||
{i: matchFnType, t: Function},
|
(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})},
|
|
||||||
]};
|
|
||||||
};
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
14
typed.js
14
typed.js
|
|
@ -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},
|
||||||
|
|
|
||||||
5
util.js
5
util.js
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue