turn the function for creating new types (or type constructors) into a DOPE function

This commit is contained in:
Joeri Exelmans 2025-03-31 17:35:30 +02:00
parent d8ca2f3999
commit a0e3aa0cb3
12 changed files with 112 additions and 58 deletions

View file

@ -23,7 +23,7 @@ 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) => {
console.log("occurring", type, typeVars); // console.log("occurring", type);
if (typeVars.has(type)) { if (typeVars.has(type)) {
// type IS a type variable: // type IS a type variable:

View file

@ -1,4 +1,4 @@
import { constructSymbol, eqSymbol, getName } from "../primitives/symbol.js"; import { eqSymbol, getName } from "../primitives/symbol.js";
import { Bool, SymbolT, Type } from "../primitives/types.js"; 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";
@ -6,11 +6,11 @@ import { typedFnType } from "../structures/types.js";
export const ModuleSymbol = {l:[ export const ModuleSymbol = {l:[
{i: SymbolT, t: Type}, {i: SymbolT, t: Type},
...typedFnType(constructSymbol, fnType => // ...typedFnType(constructSymbol, fnType =>
fnType // fnType
(String) // (String)
(SymbolT) // (SymbolT)
), // ),
...typedFnType(getName, fnType => ...typedFnType(getName, fnType =>
fnType fnType

View file

@ -1,7 +1,7 @@
// The functions are only defined here. For their types, see lib/symbol.js // The functions are only defined here. For their types, see lib/symbol.js
// The point of having an explicit constructor for SymbolT, is that we can later swap it out for something that is (de-)serializable. // Cannot turn the constructor into a DOPE function, because it is NOT PURE:
export const constructSymbol = name => Symbol(name); // export const constructSymbol = name => Symbol(name);
export const getName = symbol => symbol.description; export const getName = symbol => symbol.description;

View file

@ -1,17 +1,37 @@
// to break up dependency cycles, primitive types are defined in their own JS module // to break up dependency cycles, primitive types are defined in their own JS module
import { makeTypeConstructor } from "../type_constructor.js"; import { makeTypeConstructor } from "../type_constructor.js";
import { constructSymbol } from "./symbol.js";
export const Int = makeTypeConstructor(constructSymbol('Int'), 0); const SymbolInt = Symbol('Int');
export const Bool = makeTypeConstructor(constructSymbol('Bool'), 0); const SymbolBool = Symbol('Bool');
export const Double = makeTypeConstructor(constructSymbol('Double'), 0); const SymbolDouble = Symbol('Double');
export const Byte = makeTypeConstructor(constructSymbol('Byte'), 0); const SymbolByte = Symbol('Byte');
export const Char = makeTypeConstructor(constructSymbol('Char'), 0); const SymbolChar = Symbol('Char');
const SymbolUnit = Symbol('Unit');
const SymbolSymbol = Symbol('Symbol');
const SymbolType = Symbol('Type');
export const Int = makeTypeConstructor(SymbolInt)(0);
export const Bool = makeTypeConstructor(SymbolBool)(0);
export const Double = makeTypeConstructor(SymbolDouble)(0);
export const Byte = makeTypeConstructor(SymbolByte)(0);
export const Char = makeTypeConstructor(SymbolChar)(0);
// Unit type has only 1 instance, the empty tuple. // Unit type has only 1 instance, the empty tuple.
export const Unit = makeTypeConstructor(constructSymbol('Unit'), 0); export const Unit = makeTypeConstructor(SymbolUnit)(0);
export const SymbolT = makeTypeConstructor(constructSymbol('Symbol'), 0); export const SymbolT = makeTypeConstructor(SymbolSymbol)(0);
export const Type = makeTypeConstructor(constructSymbol('Type'), 0); export const Type = makeTypeConstructor(SymbolType)(0);
export const ModuleSymbols = {l:[
{i: SymbolInt , t: SymbolT},
{i: SymbolBool , t: SymbolT},
{i: SymbolDouble, t: SymbolT},
{i: SymbolByte , t: SymbolT},
{i: SymbolChar , t: SymbolT},
{i: SymbolUnit , t: SymbolT},
{i: SymbolSymbol, t: SymbolT},
{i: SymbolType , t: SymbolT},
]};

View file

@ -1,10 +1,10 @@
import { select, number } from '@inquirer/prompts'; import { select, number, input } from '@inquirer/prompts';
import { ModulePoint } from "../lib/point.js"; import { ModulePoint } from "../lib/point.js";
import { DefaultMap } from "../util/defaultmap.js"; 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, Type } from "../primitives/types.js"; import { Double, Int, SymbolT, Type } from "../primitives/types.js";
import { eqType } from '../type.js'; import { eqType } from '../type.js';
class Context { class Context {
@ -260,33 +260,49 @@ async function functionOptions(fn, fnT) {
async function callFunction(fn, fnT) { async function callFunction(fn, fnT) {
const {strI, strT} = prettyIT({i: fn, t: fnT}); const {strI, strT} = prettyIT({i: fn, t: fnT});
const inType = fnT.params[0]; const inType = fnT.params[0];
const choice = await (async () => { 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(inType)(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
}); });
return (n === undefined) ? "(go back)" : {i: BigInt(n), t: Int}; if (n === undefined) {
return;
}
i = BigInt(n);
} }
if (eqType(inType)(Double)) { else if (eqType(inType)(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',
}); });
return (n === undefined) ? "(go back)" : {i: n, t: Int}; if (n === undefined) {
return;
}
i = n;
}
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.");
const symbolDescr = await input({message: "enter symbol description:"});
i = Symbol(symbolDescr);
}
else {
console.log("no prompt handler for creating new", prettyT(inType));
return callFunction(fn, fnT);
} }
return select({
message: `select parameter for function ${strI} :: ${strT}`,
choices: [
"(go back)",
... [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)])),
],
});
})();
if (choice === "(go back)") {
return;
} }
const {i, t} = choice;
await apply(i, fn, fnT); await apply(i, fn, fnT);
return callFunction(fn, fnT); return callFunction(fn, fnT);
} }
@ -327,6 +343,7 @@ async function transform(i, t) {
if (choice === "(go back)") { if (choice === "(go back)") {
return; return;
} }
const {i:fn,t:fnT} = choice; const {i:fn,t:fnT} = choice;
await apply(i, fn, fnT); await apply(i, fn, fnT);
return transform(i, t); return transform(i, t);
@ -334,6 +351,7 @@ 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);
const resultType = fnT.params[1]; const 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});

View file

@ -5,6 +5,7 @@ import { ModuleByte } from "./primitives/byte.js";
import { ModuleChar } from "./primitives/char.js"; import { ModuleChar } from "./primitives/char.js";
import { ModuleDouble } from "./primitives/double.js"; import { ModuleDouble } from "./primitives/double.js";
import { ModuleInt } from "./primitives/int.js"; import { ModuleInt } from "./primitives/int.js";
import { ModuleSymbols } from "./primitives/types.js";
import { ModuleUnit } from "./primitives/unit.js"; import { ModuleUnit } from "./primitives/unit.js";
import { ModuleFunction } from "./structures/function.js"; import { ModuleFunction } from "./structures/function.js";
import { ModuleList } from "./structures/list.js"; import { ModuleList } from "./structures/list.js";
@ -18,6 +19,7 @@ export const ModuleStd = {l:[
...ModuleTyped.l, ...ModuleTyped.l,
...ModuleTypeConstructor.l, ...ModuleTypeConstructor.l,
...ModuleSymbols.l,
// Primitive types // Primitive types
...ModuleBool.l, ...ModuleBool.l,

View file

@ -2,8 +2,6 @@ import { Type } from "../primitives/types.js";
import { typedFnType } from "./types.js"; import { typedFnType } from "./types.js";
import { fnType } from "./types.js"; import { fnType } from "./types.js";
// export const pprintFn = fnT => ``
export const ModuleFunction = {l:[ export const ModuleFunction = {l:[
// binary type constructor: Type -> Type -> Type // binary type constructor: Type -> Type -> Type
...typedFnType(fnType, fnType => fnType ...typedFnType(fnType, fnType => fnType

View file

@ -4,7 +4,7 @@ import { sumType, prodType, fnType } from "./types.js";
export const createNominalADT = symbol => variants => { export const createNominalADT = symbol => variants => {
makeTypeConstructor(symbol, 0, )
}; };
export const createNominalADTFnType = export const createNominalADTFnType =

View file

@ -9,7 +9,7 @@ import { getSymbol, makeTypeConstructor } from "../type_constructor.js";
// It is a cheap workaround for JS lacking customizable hash-functions and equality-testing-functions. // 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, ...) // This same pattern is repeated throughout the code for all non-nullary type constructors (list, sum, product, ...)
const symbolFunction = Symbol('Function'); const symbolFunction = Symbol('Function');
export const fnType = makeTypeConstructor(symbolFunction, 2); export const fnType = makeTypeConstructor(symbolFunction)(2);
export const isFunction = type => getSymbol(type) === symbolFunction; export const isFunction = type => getSymbol(type) === symbolFunction;
@ -40,25 +40,26 @@ export const typedFnType2 = callback => {
// Sum type // Sum type
const symbolSum = Symbol("Sum"); const symbolSum = Symbol("Sum");
export const sumType = makeTypeConstructor(symbolSum, 2); export const sumType = makeTypeConstructor(symbolSum)(2);
// Product type // Product type
const symbolProduct = Symbol("Product"); const symbolProduct = Symbol("Product");
export const prodType = makeTypeConstructor(symbolProduct, 2); export const prodType = makeTypeConstructor(symbolProduct)(2);
// List type // List type
const symbolList = Symbol('List'); const symbolList = Symbol('List');
export const lsType = makeTypeConstructor(symbolList, 1); export const lsType = makeTypeConstructor(symbolList)(1);
// Set type // Set type
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);
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)}`;

View file

@ -6,7 +6,7 @@ import { eqDictType } from "../typeclasses/eq_type.js";
import { fnType, setType } from "./types.js"; import { fnType, setType } from "./types.js";
const symbolVersioned = Symbol("Versioned"); 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 => value => {

View file

@ -1,15 +1,30 @@
import { DefaultMap } from "./util/defaultmap.js"; import { DefaultMap } from "./util/defaultmap.js";
// Creates a new nominal type const nullaryTypeConstructors = new DefaultMap(symbol => ({symbol, params: []})); // symbol -> 0-ary type constructor (= a type, basically)
export const makeTypeConstructor = (name, n_ary, params = []) => {
if (n_ary === 0) { const makeTypeConstructorInternal = (symbol, n_ary, params = []) => {
return { symbol: Symbol(name), params }; // console.log("n_ary:", n_ary);
if (n_ary === 0 || n_ary === 0n) {
// a bit dirty, but otherwise OK
if (params.length > 0) {
const result = { symbol, params };
// console.log("result:", result);
return result;
}
else {
const result = nullaryTypeConstructors.getdefault(symbol, true)
// console.log("result:", result);
return result;
}
} }
else { else {
const m = new DefaultMap(typeParam => makeTypeConstructor(name, n_ary - 1, params.concat([typeParam]))); const m = new DefaultMap(typeParam => makeTypeConstructorInternal(symbol, n_ary - 1, params.concat([typeParam])));
return typeParam => m.getdefault(typeParam, true); return typeParam => m.getdefault(typeParam, true);
} }
}; };
// Creates a new nominal type
export const makeTypeConstructor = symbol => nAry => makeTypeConstructorInternal(symbol, nAry);
export const getSymbol = type => type.symbol; export const getSymbol = type => type.symbol;
export const getParams = type => ({ l: type.params }); export const getParams = type => ({ l: type.params });

View file

@ -29,15 +29,15 @@ export const ModuleEq = {l:[
const eq = x => y => deepEqual(x,y); const eq = x => y => deepEqual(x,y);
const EqDict = {eq}; const eqDict = {eq};
export const EqInstances = new Map([ export const EqInstances = new Map([
[Int , EqDict], [Int , eqDict],
[Bool , EqDict], [Bool , eqDict],
[Double , EqDict], [Double , eqDict],
[Byte , EqDict], [Byte , eqDict],
[Char , EqDict], [Char , eqDict],
[Unit , EqDict], [Unit , eqDict],
[Type , EqDict], [Type , eqDict],
[SymbolT, EqDict], [SymbolT, eqDict],
]); ]);