interactive prompt can handle polymorphic types

This commit is contained in:
Joeri Exelmans 2025-04-02 15:49:43 +02:00
parent a0e3aa0cb3
commit 4a4983f693
20 changed files with 485 additions and 276 deletions

View file

@ -1,160 +1,180 @@
import { eqType } from "../type.js"; import { eqType } from "../primitives/type.js";
import { zip } from "../util/util.js"; import { zip } from "../util/util.js";
import { pretty } from '../util/pretty.js'; import { pretty } from '../util/pretty.js';
import { prettyT } from "../structures/types.js";
// constructor for generic types // constructor for generic types
// for instance, the type: // for instance, the type:
// a -> a -> Bool // ∀a: a -> a -> Bool
// is created by // is created by
// makeGeneric(a => fnType({in: a, out: fnType({in: a, out: Bool})})) // makeGeneric(a => fnType(a)(fnType(a)(Bool)))
export const makeGeneric = callback => { export const makeGeneric = callback => {
// type variables to make available: // type variables to make available:
const typeVars = ['a', 'b', 'c', 'd', 'e'].map( const typeVars = ['a', 'b', 'c', 'd', 'e'].map(Symbol);
letter => ({
symbol: Symbol(letter),
params: [],
}));
const type = callback(...typeVars); const type = callback(...typeVars);
return { return onlyOccurring(type, new Set(typeVars));
typeVars: occurring(type, new Set(typeVars)),
type,
};
}; };
export const onlyOccurring = (type, typeVars) => ({
typeVars: occurring(type, typeVars),
type,
});
// 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) => {
// console.log("occurring", type); // console.log("occurring", type);
if (typeVars.has(type)) { if (typeVars.has(type)) {
// type IS a type variable: // type IS a type variable:
return new Set([type]); return new Set([type]);
} }
return new Set(type.params.flatMap(p => [...occurring(p, typeVars)])); return new Set(type.params.flatMap(p => [...occurring(p, typeVars)]));
} };
// Merge 2 substitution-mappings, uni-directional. // Merge 2 substitution-mappings, uni-directional.
const mergeOneWay = (m1, m2) => { const mergeOneWay = (m1, m2) => {
const m1copy = new Map(m1); const m1copy = new Map(m1);
const m2copy = new Map(m2); const m2copy = new Map(m2);
for (const [key1, val1] of m1copy.entries()) { for (const [var1, typ1] of m1copy.entries()) {
if (m2copy.has(val1)) { if (m2copy.has(typ1)) {
m1copy.set(key1, m2.get(val1)); // typ1 is a typeVar for which we also have a substitution
m2copy.delete(val1); // -> fold substitutions
return [false, m1copy, m2copy, new Set([val1])]; m1copy.set(var1, m2.get(typ1));
m2copy.delete(typ1);
return [false, m1copy, m2copy, new Set([typ1])];
} }
} }
return [true, m1copy, m2copy, new Set()]; // stable return [true, m1copy, m2copy, new Set()]; // stable
} };
const checkConflict = (m1, m2) => {
for (const [var1, typ1] of m1) {
if (m2.has(var1)) {
const other = m2.get(var1);
if (!eqType(typ1, other)) {
throw new Error(`conflicting substitution: ${pretty(typ1)}vs. ${pretty(other)}`);
}
}
}
};
// Merge 2 substitution-mappings, bi-directional. // Merge 2 substitution-mappings, bi-directional.
export const mergeTwoWay = (m1, m2) => { export const mergeTwoWay = (m1, m2) => {
// check for conflicts: // console.log("mergeTwoWay", {m1, m2});
for (const [typeVar, actual] of m1) { checkConflict(m1, m2);
if (m2.has(typeVar)) { // checkConflict(m2, m1); // <- don't think this is necessary...
const other = m2.get(typeVar);
if (!eqType(actual, other)) {
throw new Error(`conflicting substitution: ${pretty(actual)}vs. ${pretty(other)}`);
}
}
}
// actually merge // actually merge
let stable = false; let stable = false;
let deletedTypeVars = new Set(); let deleted = new Set();
while (!stable) { while (!stable) {
let d; let d;
// notice we swap m2 and m1, so the rewriting can happen both ways: // notice we swap m2 and m1, so the rewriting can happen both ways:
[stable, m2, m1, d] = mergeOneWay(m1, m2); [stable, m2, m1, d] = mergeOneWay(m1, m2);
deletedTypeVars = deletedTypeVars.union(d); deleted = deleted.union(d);
} }
return [new Map([...m1, ...m2]), deletedTypeVars]; const result = {
} substitutions: new Map([...m1, ...m2]),
deleted, // deleted type variables
};
// console.log("mergeTwoWay result =", result);
return result;
};
// 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 = ( const unifyInternal = (typeVars, fType, aType) => {
{typeVars: formalTypeVars, type: formalType}, // console.log("unify", pretty({typeVars, fType, aType}));
{typeVars: actualTypeVars, type: actualType},
) => {
// console.log("unify", pretty({formalTypeVars, formalType, actualTypeVars, actualType}));
if (formalTypeVars.has(formalType)) { if (typeVars.has(fType)) {
// simplest case: formalType is a type paramater // simplest case: formalType is a type paramater
// => substitute with actualType // => substitute with actualType
// console.log("assign actual to formal"); // console.log("assign actual to formal");
return { return {
substitutions: new Map([[formalType, actualType]]), substitutions: new Map([[fType, aType]]),
typeVars: new Set([ genericType: {
...actualTypeVars, typeVars: typeVars.difference(new Set([fType])),
...[...formalTypeVars].filter(a => a !== formalType), type: aType,
]), },
type: actualType,
}; };
} }
if (actualTypeVars.has(actualType)) { if (typeVars.has(aType)) {
// same as above, but in the other direction // same as above, but in the other direction
// console.log("assign formal to actual"); // console.log("assign formal to actual");
return { return {
substitutions: new Map([[actualType, formalType]]), substitutions: new Map([[aType, fType]]),
typeVars: new Set([ genericType: {
...[...actualTypeVars].filter(a => a !== actualType), typeVars: typeVars.difference(new Set([aType])),
...formalTypeVars, type: fType,
]), },
type: formalType,
}; };
} }
// recursively unify // recursively unify
if (formalType.symbol !== actualType.symbol) { if (fType.symbol !== aType.symbol) {
throw new Error(`cannot unify ${pretty(formalType.symbol)} and ${pretty(actualType.symbol)}`); throw new Error(`cannot unify ${prettyT(fType)} and ${prettyT(aType)}`);
} }
else { else {
// console.log("symbols match - unify recursively", formalType.symbol); // console.log("symbols match - unify recursively", formal.symbol);
const unifiedParams = zip(formalType.params, actualType.params).map(([formalParam, actualParam]) => unify({typeVars: formalTypeVars, type: formalParam}, {typeVars: actualTypeVars, type: actualParam})); const unifiedParams =
const [unifiedSubstitusions, deleted] = unifiedParams.reduce(([substitutionsSoFar, deletedSoFar], cur) => { zip(fType.params, aType.params)
// console.log('merging', substitutionsSoFar, cur.substitutions); .map(([fParam, aParam]) => unifyInternal(typeVars, fParam, aParam));
const [newSubstitutions, deleted] = mergeTwoWay(substitutionsSoFar, cur.substitutions); const {substitutions, deleted} =
return [newSubstitutions, deletedSoFar.union(deleted)]; unifiedParams.reduce(({substitutions: s, deleted: d}, cur) => {
}, [new Map(), new Set()]); // console.log('merging', s, cur.substitutions);
const unifiedTypeVars = new Set([ const {substitutions, deleted} = mergeTwoWay(s, cur.substitutions);
...actualTypeVars,
...formalTypeVars,
].filter(a => !unifiedSubstitusions.has(a) && !deleted.has(a)));
return { return {
substitutions: unifiedSubstitusions, substitutions,
typeVars: unifiedTypeVars, deleted: deleted.union(d),
};
}, { substitutions: new Map(), deleted: new Set() });
// console.log(pretty({unifiedParams}));
return {
substitutions,
genericType: {
typeVars: typeVars.difference(substitutions).difference(deleted),
type: { type: {
symbol: formalType.symbol, symbol: fType.symbol,
params: unifiedParams.map(p => p.type), params: unifiedParams.map(p => p.genericType.type),
},
}, },
}; };
} }
}; };
export const unify = (fGenericType, aGenericType) => {
const {genericType} = unifyInternal(
fGenericType.typeVars.union(aGenericType.typeVars),
fGenericType.type,
aGenericType.type,
)
return genericType;
}
export const substitute = (type, substitutions) => { export const substitute = (type, substitutions) => {
// console.log("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.params.length === 0) { if (typeof type === "symbol") {
// Attention: there's a reason why we have this special case. return type; // nothing to substitute here
// Types are compared by object ID, so we don't want to create a new object for a type that takes no type parameters (then the newly create type would differ).
// Should fix this some day.
return type;
} }
return { return {
symbol: type.symbol, symbol: type.symbol,
params: type.params.map(p => substitute(p, substitutions)), params: type.params.map(p => substitute(p, substitutions)),
}; };
} };
export const assign = (genFnType, paramType) => { export const assign = (genFnType, paramType) => {
const [inType, outType] = genFnType.type.params; const [inType, outType] = genFnType.type.params;
const matchedInType = unify({ const allTypeVars = genFnType.typeVars.union(paramType.typeVars)
typeVars: genFnType.typeVars, const {substitutions} = unifyInternal(allTypeVars, inType, paramType.type);
type: inType, const substitutedOutType = substitute(outType, substitutions);
}, paramType); return onlyOccurring(substitutedOutType, allTypeVars);
const substitutedOutType = substitute(outType, matchedInType.substitutions);
return {
typeVars: matchedInType.typeVars,
type: substitutedOutType,
};
}; };
export const assignFn = (genFnType, paramType) => {
const [inType] = genFnType.type.params;
const allTypeVars = genFnType.typeVars.union(paramType.typeVars)
const {substitutions} = unifyInternal(allTypeVars, inType, paramType.type);
// console.log({genFnType: prettyT(genFnType), paramType: prettyT(paramType), substitutions})
const substitutedFnType = substitute(genFnType.type, substitutions);
return onlyOccurring(substitutedFnType, allTypeVars);
}

View file

@ -1,21 +1,20 @@
import { Bool, Int } from "../primitives/types.js"; import { Bool, Int } from "../primitives/types.js";
import { fnType, lsType } from "../structures/types.js"; import { fnType, lsType, prettyT } from "../structures/types.js";
import { assign, makeGeneric, unify } from "./generics.js"; import { assign, makeGeneric, unify } from "./generics.js";
import { pretty } from "../util/pretty.js";
// a -> Int // a -> Int
const a_to_Int = makeGeneric(a => fnType(a)(Int)); const a_to_Int = makeGeneric(a => fnType(a)(Int));
// Bool -> Int // Bool -> Int
const Bool_to_Int = makeGeneric(() => fnType(lsType(Bool))(Int)); const Bool_to_Int = makeGeneric(() => fnType(lsType(Bool))(Int));
console.log("should be: [Bool] -> Int") console.log("should be: [Bool] -> Int")
console.log(pretty(unify(a_to_Int, Bool_to_Int))); console.log(prettyT(unify(a_to_Int, Bool_to_Int)));
// (a -> a) -> b // (a -> a) -> b
const fnType2 = makeGeneric((a,b) => fnType(fnType(a)(a))(b)); const fnType2 = makeGeneric((a,b) => fnType(fnType(a)(a))(b));
// (Bool -> Bool) -> a // (Bool -> Bool) -> a
const fnType3 = makeGeneric(a => fnType(fnType(Bool)(Bool))(a)); const fnType3 = makeGeneric(a => fnType(fnType(Bool)(Bool))(a));
console.log("should be: (Bool -> Bool) -> a"); console.log("should be: (Bool -> Bool) -> a");
console.log(pretty(unify(fnType2, fnType3))); console.log(prettyT(unify(fnType2, fnType3)));
// (a -> b) -> [a] -> [b] // (a -> b) -> [a] -> [b]
const mapFnType = makeGeneric((a,b) => const mapFnType = makeGeneric((a,b) =>
@ -23,10 +22,10 @@ const mapFnType = makeGeneric((a,b) =>
(fnType(a)(b)) (fnType(a)(b))
(fnType(lsType(a))(lsType(b)))) (fnType(lsType(a))(lsType(b))))
// a -> a // a -> a
const idFnType = makeGeneric(a => const idFnType = makeGeneric((_,__,c) =>
fnType(a)(a)); fnType(c)(c));
console.log("should be: [a] -> [a]"); console.log("should be: [c] -> [c]");
console.log(pretty(assign(mapFnType, idFnType))); console.log(prettyT(assign(mapFnType, idFnType)));
// (a -> Int) -> [a] -> a // (a -> Int) -> [a] -> a
const weirdFnType = makeGeneric(a => const weirdFnType = makeGeneric(a =>
@ -40,4 +39,4 @@ const weirdFnType = makeGeneric(a =>
// a := b // a := b
// b := Int // b := Int
console.log("should be: [Int] -> Int"); console.log("should be: [Int] -> Int");
console.log(pretty(assign(weirdFnType, idFnType))); console.log(prettyT(assign(weirdFnType, idFnType)));

View file

@ -3,20 +3,17 @@ import { Bool, SymbolT, Type } from "../primitives/types.js";
import { String } from "../structures/list.js"; import { String } from "../structures/list.js";
import { typedFnType } from "../structures/types.js"; import { typedFnType } from "../structures/types.js";
// The way instances of SymbolT are currently encoded, their constructor is not a valid DOPE function, because it is impure.
// The only way to construct symbols is to do it in JS code.
export const ModuleSymbol = {l:[ export const ModuleSymbol = {l:[
{i: SymbolT, t: Type}, {i: SymbolT, t: Type},
// ...typedFnType(constructSymbol, fnType =>
// fnType
// (String)
// (SymbolT)
// ),
...typedFnType(getName, fnType => ...typedFnType(getName, fnType =>
fnType fnType
(SymbolT) (SymbolT)
(String) (String)
), ),
...typedFnType(eqSymbol, fnType => fnType(SymbolT, fnType(SymbolT, Bool))), ...typedFnType(eqSymbol, fnType => fnType(SymbolT)(fnType(SymbolT)(Bool))),
]}; ]};

View file

@ -0,0 +1,21 @@
import { constructorLeft, constructorRight } from "../structures/sum.js";
import { fnType, setType, sumType, typedFnType } from "../structures/types.js";
import { GenericType, SymbolT, Type, Unit } from "./types.js";
import { unit } from "./unit.js";
export const getType = genericType => genericType.type;
export const getTypeVars = genericType => genericType.typeVars;
export const toNonGeneric = genericType => (genericType.typeVars.size === 0)
? constructorRight(genericType.type)
: constructorLeft(unit);
export const ModuleGenericType = {l:[
{i: GenericType, t: Type},
...typedFnType(getType, fnType => fnType(GenericType)(Type)),
...typedFnType(getTypeVars, fnType => fnType(GenericType)(setType(SymbolT))),
...typedFnType(toNonGeneric, fnType => fnType(GenericType)(sumType(Unit)(Type))),
]};

View file

@ -1,7 +1,7 @@
import { Bool, SymbolT, Type } from "./primitives/types.js"; import { Bool, SymbolT, Type } from "./types.js";
import { isFunction, lsType, typedFnType } from "./structures/types.js"; import { isFunction, lsType, typedFnType } from "../structures/types.js";
import { getSymbol, getParams } from "./type_constructor.js"; import { getSymbol, getParams } from "../type_constructor.js";
import { deepEqual } from "./util/util.js"; import { deepEqual } from "../util/util.js";
// we can test whether types are equal: // we can test whether types are equal:
export const eqType = t1 => t2 => deepEqual(t1, t2); export const eqType = t1 => t2 => deepEqual(t1, t2);

View file

@ -10,6 +10,7 @@ const SymbolChar = Symbol('Char');
const SymbolUnit = Symbol('Unit'); const SymbolUnit = Symbol('Unit');
const SymbolSymbol = Symbol('Symbol'); const SymbolSymbol = Symbol('Symbol');
const SymbolType = Symbol('Type'); const SymbolType = Symbol('Type');
const SymbolGenericType = Symbol('GenericType');
export const Int = makeTypeConstructor(SymbolInt)(0); export const Int = makeTypeConstructor(SymbolInt)(0);
export const Bool = makeTypeConstructor(SymbolBool)(0); export const Bool = makeTypeConstructor(SymbolBool)(0);
@ -24,6 +25,8 @@ export const SymbolT = makeTypeConstructor(SymbolSymbol)(0);
export const Type = makeTypeConstructor(SymbolType)(0); export const Type = makeTypeConstructor(SymbolType)(0);
export const GenericType = makeTypeConstructor(SymbolGenericType)(0);
export const ModuleSymbols = {l:[ export const ModuleSymbols = {l:[
{i: SymbolInt , t: SymbolT}, {i: SymbolInt , t: SymbolT},
@ -34,4 +37,5 @@ export const ModuleSymbols = {l:[
{i: SymbolUnit , t: SymbolT}, {i: SymbolUnit , t: SymbolT},
{i: SymbolSymbol, t: SymbolT}, {i: SymbolSymbol, t: SymbolT},
{i: SymbolType , t: SymbolT}, {i: SymbolType , t: SymbolT},
{i: SymbolGenericType, t: SymbolT},
]}; ]};

View file

@ -1,10 +1,12 @@
import { typedFnType } from "../structures/types.js"; import { typedFnType } from "../structures/types.js";
import { Bool, Type, Unit } from "./types.js"; import { Bool, Type, Unit } from "./types.js";
const eqUnit = x => y => x === y; export const eqUnit = x => y => x === y;
export const unit = {};
export const ModuleUnit = {l:[ export const ModuleUnit = {l:[
{i: {}, t: Unit}, {i: unit, t: Unit},
{i: Unit, t: Type}, {i: Unit, t: Type},

View file

@ -4,8 +4,37 @@ import { DefaultMap } from "../util/defaultmap.js";
import { pretty } from '../util/pretty.js'; import { pretty } from '../util/pretty.js';
import { isFunction, prettyT } from '../structures/types.js'; import { isFunction, prettyT } from '../structures/types.js';
import { ModuleStd } from '../stdlib.js'; import { ModuleStd } from '../stdlib.js';
import { Double, Int, SymbolT, Type } from "../primitives/types.js"; import { Double, GenericType, Int, SymbolT, Type } from "../primitives/types.js";
import { eqType } from '../type.js'; import { eqType } from '../primitives/type.js';
import { Any } from '../typed.js';
import { assign, assignFn, makeGeneric, onlyOccurring } from '../generics/generics.js';
// import {emitKeypressEvents} from 'node:readline';
// // Configure readline to read from stdin
// emitKeypressEvents(process.stdin);
// process.stdin.setRawMode(true);
// console.log('Press any key (ESC to exit)...');
// process.stdin.on('keypress', (str, key) => {
// if (key.name === 'escape') {
// console.log('Escape key pressed!');
// process.exit();
// }
// });
const prettyIT = ({i, t}) => ({
strI: isType(i) ? prettyT(i) : pretty(i),
strT: prettyT(t),
// strI: pretty(i),
// strT: pretty(t),
});
const isType = i => i.typeVars || i.symbol;
// ctx.types.getdefault(i).has(Type)
// || ctx.types.getdefault(i).has(GenericType);
class Context { class Context {
constructor(mod) { constructor(mod) {
@ -14,64 +43,44 @@ class Context {
this.instances = new DefaultMap(() => new Set()); // type to instance this.instances = new DefaultMap(() => new Set()); // type to instance
for (const {i, t} of mod.l) { for (const {i, t} of mod.l) {
const {strI, strT} = prettyIT({i,t})
// console.log(strI, '::', strT);
this.types.getdefault(i, true).add(t); this.types.getdefault(i, true).add(t);
this.types.getdefault(i, true).add(Any);
this.instances.getdefault(t, true).add(i); this.instances.getdefault(t, true).add(i);
this.instances.getdefault(Any, true).add(i);
} }
const addIfFunctionType = (t, originalT, add) => {
this.functionsFrom = new DefaultMap(() => new Set()); // type to outgoing function
this.functionsTo = new DefaultMap(() => new Set()); // type to incoming function
for (const t of this.instances.m.keys()) {
if (isFunction(t)) { if (isFunction(t)) {
// 't' is a function signature for (const fn of this.instances.getdefault(originalT)) {
for (const fn of this.instances.getdefault(t)) { add(fn);
this.functionsFrom.getdefault(t.params[0], true).add(fn);
this.functionsTo .getdefault(t.params[1], true).add(fn);
} }
} }
} }
this.functions = [];
// this.typeVarAssigns = new Map(); for (const type of this.instances.getdefault(Type)) {
addIfFunctionType(type, type, fn => this.functions.push({fn, type}));
// for (const t of this.instances.m.keys()) { }
// if (t.typeVars) { this.genericFunctions = [];
// for (const t2 of this.instances.m.keys()) { for (const genericType of this.instances.getdefault(GenericType)) {
// const genericT2 = (t2.typeVars === undefined) addIfFunctionType(genericType.type, genericType, fn => this.genericFunctions.push({fn, genericType}));
// ? makeGeneric(() => t2) }
// : t2;
// try {
// const unification = unify(t, t2);
// console.log(unification);
// } catch (e) {
// // skip
// }
// }
// }
// }
} }
addToCtx({i, t}) { addToCtx({i, t}) {
return new Context({l:[ return new Context({l:[
...this.mod.l, ...this.mod.l,
{i, t}, {i, t},
]}) ]});
} }
} }
let ctx = new Context({l:[ let ctx = new Context({l:[
...ModuleStd.l, ...ModuleStd.l,
...ModulePoint.l, ...ModulePoint.l,
]}); ]});
const prettyIT = ({i, t}) => ({
strI: isType(i) ? prettyT(i) : pretty(i),
strT: prettyT(t),
// strI: pretty(i),
// strT: pretty(t),
});
const toChoices = ([i, types]) => { const toChoices = ([i, types]) => {
return [...types].map(t => { return [...types].map(t => {
const {strI, strT} = prettyIT({i, t}); const {strI, strT} = prettyIT({i, t});
@ -82,50 +91,33 @@ const toChoices = ([i, types]) => {
short: `${strI} :: ${strT}`, short: `${strI} :: ${strT}`,
}; };
}); });
} };
const isType = i => ctx.types.getdefault(i).has(Type);
async function topPrompt() { async function topPrompt() {
const action = await select({ const action = await select({
message: "What do you want to do?", message: "What do you want to do?",
choices: [ choices: [
"list all types", "list all types",
"list all generic types",
"list all functions", "list all functions",
"list all", "list all",
], ],
}); });
if (action === "list all types") { if (action === "list all types") {
await listInstances(Type); await listInstances(Type);
// await listAllTypes(); }
if (action === "list all generic types") {
await listInstances(GenericType);
} }
if (action === "list all functions") { if (action === "list all functions") {
await listAllFunctions(); await listAllFunctions();
} }
if (action === "list all") { if (action === "list all") {
await listAllInstances(); await listAll();
} }
return topPrompt(); return topPrompt();
} }
// async function listAllTypes() {
// const choice = await select({
// message: "select type:",
// choices: [
// "(go back)",
// ...[...ctx.instances.m.keys()].map(t => ({
// value: t,
// name: prettyT(t),
// })),
// ]
// });
// if (choice === "(go back)") {
// return;
// }
// await typeOptions(choice);
// return listAllTypes();
// }
async function listAllFunctions() { async function listAllFunctions() {
const choice = await select({ const choice = await select({
message: "select function:", message: "select function:",
@ -150,11 +142,12 @@ async function listAllFunctions() {
return listAllFunctions(); return listAllFunctions();
} }
async function typeOptions(t) { async function typeOptions(t, tt) {
const choice = await select({ const choice = await select({
message: `actions for type ${prettyT(t)} :: Type`, message: `actions for type ${prettyT(t)} :: ${prettyT(tt)}`,
choices: [ choices: [
"(go back)", "(go back)",
"create instance",
"list instances", "list instances",
// "list outgoing functions", // "list outgoing functions",
// "list incoming functions", // "list incoming functions",
@ -165,6 +158,13 @@ async function typeOptions(t) {
if (choice === "(go back)") { if (choice === "(go back)") {
return; return;
} }
else if (choice === "create instance") {
const i = await createInstance(t);
if (i !== undefined) {
ctx = ctx.addToCtx({i, t});
return instanceOrTypeOrFnOptions({i, t});
}
}
else if (choice === "list instances") { else if (choice === "list instances") {
await listInstances(t); await listInstances(t);
} }
@ -172,15 +172,15 @@ async function typeOptions(t) {
console.log(pretty(t)); console.log(pretty(t));
} }
else if (choice === "treat as instance") { else if (choice === "treat as instance") {
await instanceOptions(t, Type) await instanceOptions(t, tt)
} }
else { else {
console.log("unimplemented:", choice); console.log("unimplemented:", choice);
} }
return typeOptions(t); return typeOptions(t, tt);
} }
async function listAllInstances() { async function listAll() {
const choice = await select({ const choice = await select({
message: `all instances:`, message: `all instances:`,
choices: [ choices: [
@ -195,11 +195,16 @@ async function listAllInstances() {
} }
async function instanceOrTypeOrFnOptions({i, t}) { async function instanceOrTypeOrFnOptions({i, t}) {
if (t.typeVars) {
if (isFunction(t.type)) {
return functionOptions(i, t);
}
}
if (isFunction(t)) { if (isFunction(t)) {
return functionOptions(i, t); return functionOptions(i, t);
} }
if (isType(i)) { if (t === Type || t === GenericType) {
return typeOptions(i); return typeOptions(i, t);
} }
return instanceOptions(i,t); return instanceOptions(i,t);
} }
@ -230,8 +235,8 @@ async function listTypes(i) {
if (choice === "(go back)") { if (choice === "(go back)") {
return; return;
} }
const {i: chosenType} = choice; const {i: chosenType, t: typeOfChosenType} = choice;
await typeOptions(chosenType); await typeOptions(chosenType, typeOfChosenType);
return listTypes(i); return listTypes(i);
} }
@ -257,23 +262,11 @@ async function functionOptions(fn, fnT) {
return functionOptions(fn, fnT); return functionOptions(fn, fnT);
} }
async function callFunction(fn, fnT) { async function createInstance(t) {
const {strI, strT} = prettyIT({i: fn, t: fnT}); if (t.typeVars && t.typeVars.size === 0) {
const inType = fnT.params[0]; t = t.type; // can treat as non-generic
const choice = await select({
message: `select parameter for function ${strI} :: ${strT}`,
choices: [
"(go back)",
"(new)",
... [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)])),
],
});
let i;
if (choice === "(go back)") {
return;
} }
if (choice === "(new)") { if (eqType(t)(Int)) {
if (eqType(inType)(Int)) {
const n = await number({ const n = await number({
message: `enter an integer (leave empty to go back):`, message: `enter an integer (leave empty to go back):`,
step: 1, // only integers step: 1, // only integers
@ -281,29 +274,86 @@ async function callFunction(fn, fnT) {
if (n === undefined) { if (n === undefined) {
return; return;
} }
i = BigInt(n); return BigInt(n);
} }
else if (eqType(inType)(Double)) { else if (eqType(t)(Double)) {
const n = await number({ const n = await number({
message: `enter a number (leave empty to go back):`, message: `enter a number (leave empty to go back):`,
step: 'any', step: 'any',
}); });
if (n === undefined) { return n;
return;
} }
i = n; else if (eqType(t)(SymbolT)) {
}
else if (eqType(inType)(SymbolT)) {
console.log("Note: you are creating a new Symbol. Even if the description matches that of another symbol (e.g., \"Int\"), a new Symbol will be created that is unique and only equal to itself."); console.log("Note: you are creating a new Symbol. Even if the description matches that of another symbol (e.g., \"Int\"), a new Symbol will be created that is unique and only equal to itself.");
const symbolDescr = await input({message: "enter symbol description:"}); const symbolDescr = await input({message: "enter symbol description:"});
i = Symbol(symbolDescr); return Symbol(symbolDescr);
} }
else { else {
console.log("no prompt handler for creating new", prettyT(inType)); console.log("no prompt handler for creating new", prettyT(t));
return callFunction(fn, fnT); }
}
async function callFunction(fn, fnT) {
const {strI, strT} = prettyIT({i: fn, t: fnT});
let choices;
let inType;
if (fnT.typeVars) {
// generic
choices = [...ctx.types.m.entries()].flatMap(([i, types]) => {
return [...types].flatMap(t => {
const genT = t.typeVars ? t : makeGeneric(() => t);
let assignedFnType;
try {
assignedFnType = assignFn(fnT, genT);
} catch (e) {
if (e.message.startsWith("cannot unify")) {
// console.warn(e);
return [];
}
throw e;
}
const assignedInType = onlyOccurring(assignedFnType.type.params[0], assignedFnType.typeVars);
if (assignedInType.typeVars.size > 0) {
return toChoices([i, [assignedInType]]);
}
else {
return toChoices([i, [assignedInType.type]]);
}
});
});
inType = onlyOccurring(fnT.type.params[0], fnT.typeVars)
}
else {
inType = fnT.params[0];
choices = [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)]));
}
const choice = await select({
message: `select parameter of type ${prettyT(inType)} for function ${strI} :: ${strT}`,
choices: [
"(go back)",
"(new)",
...choices,
],
});
let i, t;
if (choice === "(go back)") {
return;
}
else if (choice === "(new)") {
i = await createInstance(inType);
t = inType;
if (i === undefined) {
return;
} }
} }
await apply(i, fn, fnT); else {
i = choice.i;
t = choice.t;
}
const genT = t.typeVars ? t : makeGeneric(() => t);
const assignedFnType = assignFn(fnT, genT);
await apply(i, fn, assignedFnType);
return callFunction(fn, fnT); return callFunction(fn, fnT);
} }
@ -333,11 +383,34 @@ async function transform(i, t) {
const {strI, strT} = prettyIT({i, t}); const {strI, strT} = prettyIT({i, t});
// console.log(ctx.functionsFrom.getdefault(t)); // console.log(ctx.functionsFrom.getdefault(t));
const genT = t.typeVars ? t : makeGeneric(() => t);
const choice = await select({ const choice = await select({
message: `choose transformation to perform on ${strI} :: ${strT}`, message: `choose transformation to perform on ${strI} :: ${strT}`,
choices: [ choices: [
"(go back)", "(go back)",
... [...ctx.functionsFrom.getdefault(t)].flatMap(fn => toChoices([fn, ctx.types.getdefault(fn)])),
...ctx.functions
.filter(({type}) => {
// console.log(type.params[0], t);
return eqType(type.params[0])(t)
})
.flatMap(({fn, type}) => toChoices([fn, [type]])),
...ctx.genericFunctions
.flatMap(({fn, genericType}) => {
let fnType;
try {
fnType = assignFn(genericType, genT);
} catch (e) {
if (e.message.startsWith("cannot unify")) {
// console.warn(e);
return [];
}
throw e;
}
return toChoices([fn, [fnType]]);
}),
], ],
}); });
if (choice === "(go back)") { if (choice === "(go back)") {
@ -352,9 +425,18 @@ async function transform(i, t) {
async function apply(i, fn, fnT) { async function apply(i, fn, fnT) {
const result = fn(i); const result = fn(i);
// console.log(fn, '(', i, ')', '=', result); // console.log(fn, '(', i, ')', '=', result);
const resultType = fnT.params[1]; let resultType;
// console.log(fnT);
if (fnT.typeVars) {
resultType = onlyOccurring(fnT.type.params[1], fnT.typeVars);
}
else {
resultType = fnT.params[1];
}
// update context with newly produced value // update context with newly produced value
ctx = ctx.addToCtx({i: result, t: resultType}); ctx = ctx
.addToCtx({i: result, t: resultType})
.addToCtx({i: resultType, t: resultType.typeVars ? GenericType : Type});
const {strI: strResult, strT: strResultType} = prettyIT({i: result, t: resultType}); const {strI: strResult, strT: strResultType} = prettyIT({i: result, t: resultType});
console.log(`result = ${strResult} :: ${strResultType}`); console.log(`result = ${strResult} :: ${strResultType}`);
return instanceOrTypeOrFnOptions({i: result, t: resultType}); return instanceOrTypeOrFnOptions({i: result, t: resultType});

View file

@ -11,11 +11,14 @@ import { ModuleFunction } from "./structures/function.js";
import { ModuleList } from "./structures/list.js"; import { ModuleList } from "./structures/list.js";
import { ModuleProduct } from "./structures/product.js"; import { ModuleProduct } from "./structures/product.js";
import { ModuleSum } from "./structures/sum.js"; import { ModuleSum } from "./structures/sum.js";
import { ModuleType } from "./type.js"; import { ModuleType } from "./primitives/type.js";
import { ModuleTyped } from "./typed.js"; import { ModuleTyped } from "./typed.js";
import { ModuleSet } from "./structures/set.js";
import { ModuleGenericType } from "./primitives/generic_type.js";
export const ModuleStd = {l:[ export const ModuleStd = {l:[
...ModuleType.l, ...ModuleType.l,
...ModuleGenericType.l,
...ModuleTyped.l, ...ModuleTyped.l,
...ModuleTypeConstructor.l, ...ModuleTypeConstructor.l,
@ -35,4 +38,5 @@ export const ModuleStd = {l:[
...ModuleList.l, ...ModuleList.l,
...ModuleProduct.l, ...ModuleProduct.l,
...ModuleSum.l, ...ModuleSum.l,
...ModuleSet.l,
]}; ]};

View file

@ -1,16 +1,19 @@
import { typedFnType } from "./types.js"; import { typedFnType } from "./types.js";
import { Char, Type } from "../primitives/types.js"; import { Char, GenericType, Type } from "../primitives/types.js";
import { Int } from "../primitives/types.js"; import { Int } from "../primitives/types.js";
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
import { lsType } from "./types.js"; import { lsType } from "./types.js";
import { Typed } from "../typed.js"
// 'normal' implementation // 'normal' implementation
const emptyList = {l:[]}; const emptyList = {l:[]};
const emptyListType = makeGeneric(a => lsType(a));
const get = ls => i => ls.l[i]; const get = ls => i => ls.l[i];
const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)}); const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)});
const push = ls => elem => ({l:ls.l.concat([elem])}); const push = ls => elem => ({l:ls.l.concat([elem])});
export const String = lsType(Char); // alias export const String = lsType(Char); // alias
export const Module = lsType(Typed);
export const ModuleList = {l:[ export const ModuleList = {l:[
// Type -> Type // Type -> Type
@ -21,7 +24,8 @@ export const ModuleList = {l:[
), ),
// [a] // [a]
{i: emptyList, t: makeGeneric(a => lsType(a))}, {i: emptyList, t: emptyListType},
{i: emptyListType, t: GenericType},
// [a] -> Int -> a // [a] -> Int -> a
...typedFnType(get, fnType => ...typedFnType(get, fnType =>
@ -31,7 +35,7 @@ export const ModuleList = {l:[
/* out */ (fnType /* out */ (fnType
/* in */ (Int) /* in */ (Int)
/* out */ (a) /* out */ (a)
))), )), GenericType),
// [a] -> Int -> a -> [a] // [a] -> Int -> a -> [a]
...typedFnType(put, fnType => ...typedFnType(put, fnType =>
@ -44,7 +48,7 @@ export const ModuleList = {l:[
/* in */ (a) /* in */ (a)
/* out */ (lsType(a)) /* out */ (lsType(a))
) )
))), )), GenericType),
// [a] -> a -> [a] // [a] -> a -> [a]
...typedFnType(push, fnType => ...typedFnType(push, fnType =>
@ -55,8 +59,5 @@ export const ModuleList = {l:[
(a) (a)
(lsType(a)) (lsType(a))
) )
) ), GenericType),
),
// {i: String, t: Type}, // alias
]}; ]};

View file

@ -1,13 +1,76 @@
import { Any } from "../typed.js"; import { SymbolT, Type } from "../primitives/types.js";
import { String } from "./list.js"; import { makeTypeConstructor } from "../type_constructor.js";
import { sumType, prodType, fnType } from "./types.js"; import { Module, String } from "./list.js";
import { prodType, fnType, lsType } from "./types.js";
function capitalizeFirstLetter(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
export const createNominalADT = symbol => variants => { export const createStruct = (typeVars, symbol, fields) => {
makeTypeConstructor(symbol, 0, ) const makeConstructor = (remainingFields, obj={}) => {
if (remainingFields.length===0) {
return obj;
}
const {left: fieldName} = remainingFields[remainingFields.length-1];
return v => makeConstructor(
remainingFields.slice(0,-1),
Object.assign({[fieldName]: v}, obj));
};
const constructor = makeConstructor(fields);
const type = makeTypeConstructor(symbol)(typeVars.size);
const types = [ type ];
const recordFnType = inType => outType => {
const fnT = fnType(inType)(outType);
types.push(fnT);
return fnT;
}
const makeConstructorType = (remainingFields, type) => {
if (remainingFields.length===0) {
return type;
}
const {right: fieldType} = remainingFields[remainingFields.length-1];
return recordFnType(makeConstructorType(remainingFields.slice(0,-1)))(fieldType);
};
const constructorType = makeConstructorType(fields);
const functions = [
["constructor", constructor, constructorType],
...fields.map(({left: fieldName, right: fieldType}) => {
const getterName = 'get'+capitalizeFirstLetter(fieldName);
const getter = {
// stupid trick to give the JS-function a computed name.
// only important for debugging, so it says [Function: getAge] instead of [Function (anonymous)]:
[getterName]: obj => obj[fieldName],
}[getterName];
if (typeVars.has(fieldType)) {
// getterFnType = recordFnType(type)(fieldType)
}
const getterFnType = recordFnType(type)(fieldType);
return [fieldName, getter, getterFnType];
}),
];
const module = {l:[
{i: type, t: Type},
...functions.flatMap(([_, getter, getterFnType]) => [
{i: getter , t: getterFnType},
]),
...types.map(type => ({i: type, t: Type})),
]};
return {
module,
constructor,
functions: Object.fromEntries(functions),
};
}; };
export const createNominalADTFnType = export const createNominalADTModuleFnType =
fnType fnType(SymbolT)
(Any) (fnType(lsType(prodType(String)(Type)))
(); (Module));

View file

@ -1,5 +1,5 @@
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
import { Type } from "../primitives/types.js"; import { GenericType, Type } from "../primitives/types.js";
import { typedFnType } from "./types.js"; import { typedFnType } from "./types.js";
import { prodType } from "./types.js"; import { prodType } from "./types.js";
@ -29,7 +29,7 @@ export const ModuleProduct = {l: [
(b) (b)
(prodType(a)(b)) (prodType(a)(b))
) )
)), ), GenericType),
// (a, b) -> a // (a, b) -> a
...typedFnType(getLeft, fnType => ...typedFnType(getLeft, fnType =>
@ -37,7 +37,7 @@ export const ModuleProduct = {l: [
fnType fnType
(prodType(a)(b)) (prodType(a)(b))
(a) (a)
)), ), GenericType),
// (a, b) -> b // (a, b) -> b
...typedFnType(getRight, fnType => ...typedFnType(getRight, fnType =>
@ -45,5 +45,5 @@ export const ModuleProduct = {l: [
fnType fnType
(prodType(a)(b)) (prodType(a)(b))
(b) (b)
)), ), GenericType),
]}; ]};

View file

@ -1,13 +1,14 @@
import { setType, typedFnType } from "./types.js"; import { setType, typedFnType } from "./types.js";
import { Bool, Type } from "../primitives/types.js"; import { Bool, GenericType, Type } from "../primitives/types.js";
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
// 'normal' implementation // 'normal' implementation
const emptySet = new Set(); const emptySet = new Set();
const emptySetType = makeGeneric(a => setType(a));
const has = set => elem => set.has(elem); const has = set => elem => set.has(elem);
const add = set => elem => new Set([...set, elem]); const add = set => elem => new Set([...set, elem]);
export const ModuleList = {l:[ export const ModuleSet = {l:[
// Type -> Type // Type -> Type
...typedFnType(setType, fnType => ...typedFnType(setType, fnType =>
fnType fnType
@ -15,7 +16,8 @@ export const ModuleList = {l:[
/* out */ (Type) /* out */ (Type)
), ),
{i: emptySet, t: makeGeneric(a => setType(a))}, {i: emptySet , t: emptySetType},
{i: emptySetType, t: GenericType },
...typedFnType(has, fnType => ...typedFnType(has, fnType =>
makeGeneric(a => makeGeneric(a =>
@ -24,7 +26,7 @@ export const ModuleList = {l:[
/* out */ (fnType /* out */ (fnType
/* in */ (a) /* in */ (a)
/* out */ (Bool) /* out */ (Bool)
))), )), GenericType),
...typedFnType(add, fnType => ...typedFnType(add, fnType =>
makeGeneric(a => makeGeneric(a =>
@ -33,6 +35,5 @@ export const ModuleList = {l:[
/* out */ (fnType /* out */ (fnType
/* in */ (a) /* in */ (a)
/* out */ (setType(a)) /* out */ (setType(a))
))), )), GenericType),
]}; ]};

View file

@ -1,5 +1,5 @@
import { prodType } from "./types.js"; import { prodType } from "./types.js";
import { Type } from "../primitives/types.js"; import { GenericType, Type } from "../primitives/types.js";
import { typedFnType } from "./types.js"; import { typedFnType } from "./types.js";
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
import { sumType } from "./types.js"; import { sumType } from "./types.js";
@ -31,7 +31,7 @@ export const ModuleSum = {l:[
fnType fnType
(a) (a)
(sumType(a)(b)) (sumType(a)(b))
)), ), GenericType),
// b -> a | b // b -> a | b
...typedFnType(constructorRight, fnType => ...typedFnType(constructorRight, fnType =>
@ -39,7 +39,7 @@ export const ModuleSum = {l:[
fnType fnType
(b) (b)
(sumType(a)(b)) (sumType(a)(b))
)), ), GenericType),
// a | b -> (a -> c, b-> c) -> c // a | b -> (a -> c, b-> c) -> c
...typedFnType(match, fnType => ...typedFnType(match, fnType =>
@ -53,5 +53,5 @@ export const ModuleSum = {l:[
) )
(c) (c)
) )
)), ), GenericType),
]}; ]};

View file

@ -13,17 +13,8 @@ export const fnType = makeTypeConstructor(symbolFunction)(2);
export const isFunction = type => getSymbol(type) === symbolFunction; export const isFunction = type => getSymbol(type) === symbolFunction;
// Convenience function. Wrapper around function below.
export const typedFnType = (instance, callback) => {
const [t, typesOfFns] = typedFnType2(callback);
const res = [
{ i: instance, t },
...typesOfFns,
];
return res;
};
// Convenience function. Creates 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. // Convenience function. Creates 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 => { export const typedFnType = (instance, callback, typeOfType = Type) => {
const fnTs = []; const fnTs = [];
const wrappedFnType = inType => outType => { const wrappedFnType = inType => outType => {
const fnT = fnType(inType)(outType); const fnT = fnType(inType)(outType);
@ -31,10 +22,15 @@ export const typedFnType2 = callback => {
return fnT; return fnT;
}; };
const t = callback(wrappedFnType); // force evaluation const t = callback(wrappedFnType); // force evaluation
return [ if (t.typeVars && typeOfType === Type) {
t, throw new Error("you probably meant to create a GenericType");
fnTs.map(fnT => ({ i: fnT, t: Type })), }
const res = [
{ i: instance, t },
{ i: t , t: typeOfType },
// ...fnTs.map(fnT => ({ i: fnT, t: Type })),
]; ];
return res;
}; };
// Sum type // Sum type
@ -57,15 +53,21 @@ export const lsType = makeTypeConstructor(symbolList)(1);
const symbolSet = Symbol('Set'); const symbolSet = Symbol('Set');
export const setType = makeTypeConstructor(symbolSet)(1); export const setType = makeTypeConstructor(symbolSet)(1);
// Pretty print type // Pretty print type
export function prettyT(type) { export function prettyT(type) {
// console.log("pretty:", type); // console.log("pretty:", type);
if (typeof type === "symbol") {
return type.description;
}
if (type.typeVars) { if (type.typeVars) {
if (type.typeVars.size > 0) { if (type.typeVars.size > 0) {
return `${[...type.typeVars].map(prettyT).join(", ")}: ${prettyT(type.type)}`; return `(${[...type.typeVars].map(prettyT).join(", ")}): ${prettyT(type.type)}`;
} }
else {
return prettyT(type.type); return prettyT(type.type);
} }
}
if (type.symbol === symbolFunction) { if (type.symbol === symbolFunction) {
return `${prettyT(type.params[0])} -> ${prettyT(type.params[1])}`; return `${prettyT(type.params[0])} -> ${prettyT(type.params[1])}`;
} }

View file

@ -9,17 +9,22 @@ const symbolVersioned = Symbol("Versioned");
export const versionedType = makeTypeConstructor(symbolVersioned)(1); export const versionedType = makeTypeConstructor(symbolVersioned)(1);
export const constructor = parents => value => { export const constructor = parents => alternatives => {
return { parents, alternatives }; return { parents, alternatives };
} }
const constructorType = makeGeneric(a => const constructorType = makeGeneric(a =>
fnType fnType
(a)
(setType(versionedType(a))) (setType(versionedType(a)))
(fnType
(setType(a))
(versionedType(a))
)
); );
// const getValue = v => const initial = x => ({ parents: new Set(), alternatives: new Set(x) });
const initialFnType = makeGeneric(a => fnType(a)(versionedType(a)));
const eq = eqDict => vA => vB => { const eq = eqDict => vA => vB => {
return getEq(eqDict)(vA.value,vB.value) // compare values return getEq(eqDict)(vA.value,vB.value) // compare values

View file

@ -1,6 +1,11 @@
import { DefaultMap } from "./util/defaultmap.js"; import { DefaultMap } from "./util/defaultmap.js";
const nullaryTypeConstructors = new DefaultMap(symbol => ({symbol, params: []})); // symbol -> 0-ary type constructor (= a type, basically) const nullaryTypeConstructors = new DefaultMap(
// symbol -> 0-ary type constructor (= a type, basically)
symbol => ({
symbol,
params: [],
}));
const makeTypeConstructorInternal = (symbol, n_ary, params = []) => { const makeTypeConstructorInternal = (symbol, n_ary, params = []) => {
// console.log("n_ary:", n_ary); // console.log("n_ary:", n_ary);

View file

@ -1,5 +1,5 @@
import { makeGeneric } from "../generics/generics"; import { makeGeneric } from "../generics/generics";
import { SymbolT, Type, Unit } from "../primitives/types"; import { GenericType, SymbolT, Type, Unit } from "../primitives/types";
import { typedFnType } from "../structures/types"; import { typedFnType } from "../structures/types";
import { Bool, Byte, Char, Double, Int } from "../primitives/types"; import { Bool, Byte, Char, Double, Int } from "../primitives/types";
import { deepEqual } from "../util/util"; import { deepEqual } from "../util/util";
@ -22,7 +22,7 @@ export const ModuleEq = {l:[
(a) (a)
(Bool) (Bool)
) )
))), )), GenericType),
]}; ]};
// all our data (and types) are encoded such that we can test equality the same way: // all our data (and types) are encoded such that we can test equality the same way:

View file

@ -1,18 +1,22 @@
import { typedFnType } from "./structures/types.js"; import { typedFnType } from "./structures/types.js";
import { Type } from "./primitives/types.js"; import { Type } from "./primitives/types.js";
import { makeTypeConstructor } from "./type_constructor.js";
// Everything is (implicitly) typed by the Any type. // Everything is (implicitly) typed by the Any type.
export const Any = { symbol: Symbol('Any'), params: [] }; const symbolAny = Symbol('Any');
export const Any = makeTypeConstructor(symbolAny)(0);
// A type-link, connecting a value to its Type. // A type-link, connecting a value to its Type.
export const Typed = { symbol: Symbol('Typed'), params: [] }; const symbolTyped = Symbol('Typed');
export const Typed = makeTypeConstructor(symbolTyped)(0);
const getInst = lnk => lnk.i; const getInst = lnk => lnk.i;
const getType = lnk => lnk.t; const getType = lnk => lnk.t;
export const ModuleTyped = {l:[ export const ModuleTyped = {l:[
{i: Typed, t: Type}, {i: Typed, t: Type},
{i: Any , t: Type},
...typedFnType(getInst, fnType => fnType(Typed)(Type)), ...typedFnType(getInst, fnType => fnType(Typed)(Any)),
...typedFnType(getType, fnType => fnType(Typed)(Type)), ...typedFnType(getType, fnType => fnType(Typed)(Any)),
]}; ]};

View file

@ -4,4 +4,3 @@ import { inspect } from 'node:util';
export function pretty(obj) { export function pretty(obj) {
return inspect(obj, { colors: true, depth: null }); return inspect(obj, { colors: true, depth: null });
} }