From a0e3aa0cb32277dfaf7b6b8731dd2a97c2071565 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Mon, 31 Mar 2025 17:35:30 +0200 Subject: [PATCH] turn the function for creating new types (or type constructors) into a DOPE function --- generics/generics.js | 2 +- lib/symbol.js | 12 +++++----- primitives/symbol.js | 4 ++-- primitives/types.js | 38 +++++++++++++++++++++++------- scripts/main.js | 52 +++++++++++++++++++++++++++-------------- stdlib.js | 2 ++ structures/function.js | 2 -- structures/nominal.js | 2 +- structures/types.js | 11 +++++---- structures/versioned.js | 2 +- type_constructor.js | 25 ++++++++++++++++---- typeclasses/eq.js | 18 +++++++------- 12 files changed, 112 insertions(+), 58 deletions(-) diff --git a/generics/generics.js b/generics/generics.js index a854f80..400f639 100644 --- a/generics/generics.js +++ b/generics/generics.js @@ -23,7 +23,7 @@ export const makeGeneric = callback => { // From the given set of type variables, return only those that occur in the given type. export const occurring = (type, typeVars) => { - console.log("occurring", type, typeVars); + // console.log("occurring", type); if (typeVars.has(type)) { // type IS a type variable: diff --git a/lib/symbol.js b/lib/symbol.js index 1b67d34..e55a17c 100644 --- a/lib/symbol.js +++ b/lib/symbol.js @@ -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 { String } from "../structures/list.js"; import { typedFnType } from "../structures/types.js"; @@ -6,11 +6,11 @@ import { typedFnType } from "../structures/types.js"; export const ModuleSymbol = {l:[ {i: SymbolT, t: Type}, - ...typedFnType(constructSymbol, fnType => - fnType - (String) - (SymbolT) - ), + // ...typedFnType(constructSymbol, fnType => + // fnType + // (String) + // (SymbolT) + // ), ...typedFnType(getName, fnType => fnType diff --git a/primitives/symbol.js b/primitives/symbol.js index 209c51f..b0e56ad 100644 --- a/primitives/symbol.js +++ b/primitives/symbol.js @@ -1,7 +1,7 @@ // 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. -export const constructSymbol = name => Symbol(name); +// Cannot turn the constructor into a DOPE function, because it is NOT PURE: +// export const constructSymbol = name => Symbol(name); export const getName = symbol => symbol.description; diff --git a/primitives/types.js b/primitives/types.js index c798945..cfe423f 100644 --- a/primitives/types.js +++ b/primitives/types.js @@ -1,17 +1,37 @@ // to break up dependency cycles, primitive types are defined in their own JS module import { makeTypeConstructor } from "../type_constructor.js"; -import { constructSymbol } from "./symbol.js"; -export const Int = makeTypeConstructor(constructSymbol('Int'), 0); -export const Bool = makeTypeConstructor(constructSymbol('Bool'), 0); -export const Double = makeTypeConstructor(constructSymbol('Double'), 0); -export const Byte = makeTypeConstructor(constructSymbol('Byte'), 0); -export const Char = makeTypeConstructor(constructSymbol('Char'), 0); +const SymbolInt = Symbol('Int'); +const SymbolBool = Symbol('Bool'); +const SymbolDouble = Symbol('Double'); +const SymbolByte = Symbol('Byte'); +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. -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}, +]}; \ No newline at end of file diff --git a/scripts/main.js b/scripts/main.js index af558e9..e84803c 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -1,10 +1,10 @@ -import { select, number } from '@inquirer/prompts'; +import { select, number, input } from '@inquirer/prompts'; import { ModulePoint } from "../lib/point.js"; import { DefaultMap } from "../util/defaultmap.js"; import { pretty } from '../util/pretty.js'; import { isFunction, prettyT } from '../structures/types.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'; class Context { @@ -260,33 +260,49 @@ async function functionOptions(fn, fnT) { async function callFunction(fn, fnT) { const {strI, strT} = prettyIT({i: fn, t: fnT}); 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)) { const n = await number({ message: `enter an integer (leave empty to go back):`, 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({ message: `enter a number (leave empty to go back):`, 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); return callFunction(fn, fnT); } @@ -327,6 +343,7 @@ async function transform(i, t) { if (choice === "(go back)") { return; } + const {i:fn,t:fnT} = choice; await apply(i, fn, fnT); return transform(i, t); @@ -334,6 +351,7 @@ async function transform(i, t) { async function apply(i, fn, fnT) { const result = fn(i); + // console.log(fn, '(', i, ')', '=', result); const resultType = fnT.params[1]; // update context with newly produced value ctx = ctx.addToCtx({i: result, t: resultType}); diff --git a/stdlib.js b/stdlib.js index fda246d..4f28010 100644 --- a/stdlib.js +++ b/stdlib.js @@ -5,6 +5,7 @@ import { ModuleByte } from "./primitives/byte.js"; import { ModuleChar } from "./primitives/char.js"; import { ModuleDouble } from "./primitives/double.js"; import { ModuleInt } from "./primitives/int.js"; +import { ModuleSymbols } from "./primitives/types.js"; import { ModuleUnit } from "./primitives/unit.js"; import { ModuleFunction } from "./structures/function.js"; import { ModuleList } from "./structures/list.js"; @@ -18,6 +19,7 @@ export const ModuleStd = {l:[ ...ModuleTyped.l, ...ModuleTypeConstructor.l, + ...ModuleSymbols.l, // Primitive types ...ModuleBool.l, diff --git a/structures/function.js b/structures/function.js index fbde126..38424df 100644 --- a/structures/function.js +++ b/structures/function.js @@ -2,8 +2,6 @@ import { Type } from "../primitives/types.js"; import { typedFnType } from "./types.js"; import { fnType } from "./types.js"; -// export const pprintFn = fnT => `` - export const ModuleFunction = {l:[ // binary type constructor: Type -> Type -> Type ...typedFnType(fnType, fnType => fnType diff --git a/structures/nominal.js b/structures/nominal.js index 564cea7..f92f089 100644 --- a/structures/nominal.js +++ b/structures/nominal.js @@ -4,7 +4,7 @@ import { sumType, prodType, fnType } from "./types.js"; export const createNominalADT = symbol => variants => { - + makeTypeConstructor(symbol, 0, ) }; export const createNominalADTFnType = diff --git a/structures/types.js b/structures/types.js index a435d27..763b3c5 100644 --- a/structures/types.js +++ b/structures/types.js @@ -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. // This same pattern is repeated throughout the code for all non-nullary type constructors (list, sum, product, ...) const symbolFunction = Symbol('Function'); -export const fnType = makeTypeConstructor(symbolFunction, 2); +export const fnType = makeTypeConstructor(symbolFunction)(2); export const isFunction = type => getSymbol(type) === symbolFunction; @@ -40,25 +40,26 @@ export const typedFnType2 = callback => { // Sum type const symbolSum = Symbol("Sum"); -export const sumType = makeTypeConstructor(symbolSum, 2); +export const sumType = makeTypeConstructor(symbolSum)(2); // Product type const symbolProduct = Symbol("Product"); -export const prodType = makeTypeConstructor(symbolProduct, 2); +export const prodType = makeTypeConstructor(symbolProduct)(2); // List type const symbolList = Symbol('List'); -export const lsType = makeTypeConstructor(symbolList, 1); +export const lsType = makeTypeConstructor(symbolList)(1); // Set type const symbolSet = Symbol('Set'); -export const setType = makeTypeConstructor(symbolSet, 1); +export const setType = makeTypeConstructor(symbolSet)(1); // Pretty print type export function prettyT(type) { + // console.log("pretty:", type); if (type.typeVars) { if (type.typeVars.size > 0) { return `∀${[...type.typeVars].map(prettyT).join(", ")}: ${prettyT(type.type)}`; diff --git a/structures/versioned.js b/structures/versioned.js index f7f20a1..c683465 100644 --- a/structures/versioned.js +++ b/structures/versioned.js @@ -6,7 +6,7 @@ import { eqDictType } from "../typeclasses/eq_type.js"; import { fnType, setType } from "./types.js"; const symbolVersioned = Symbol("Versioned"); -export const versionedType = makeTypeConstructor(symbolVersioned, 1); +export const versionedType = makeTypeConstructor(symbolVersioned)(1); export const constructor = parents => value => { diff --git a/type_constructor.js b/type_constructor.js index 8fe9925..291e4d1 100644 --- a/type_constructor.js +++ b/type_constructor.js @@ -1,15 +1,30 @@ import { DefaultMap } from "./util/defaultmap.js"; -// Creates a new nominal type -export const makeTypeConstructor = (name, n_ary, params = []) => { - if (n_ary === 0) { - return { symbol: Symbol(name), params }; +const nullaryTypeConstructors = new DefaultMap(symbol => ({symbol, params: []})); // symbol -> 0-ary type constructor (= a type, basically) + +const makeTypeConstructorInternal = (symbol, n_ary, 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 { - 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); } }; +// Creates a new nominal type +export const makeTypeConstructor = symbol => nAry => makeTypeConstructorInternal(symbol, nAry); + export const getSymbol = type => type.symbol; export const getParams = type => ({ l: type.params }); diff --git a/typeclasses/eq.js b/typeclasses/eq.js index 64e2b6d..42535cd 100644 --- a/typeclasses/eq.js +++ b/typeclasses/eq.js @@ -29,15 +29,15 @@ export const ModuleEq = {l:[ const eq = x => y => deepEqual(x,y); -const EqDict = {eq}; +const eqDict = {eq}; export const EqInstances = new Map([ - [Int , EqDict], - [Bool , EqDict], - [Double , EqDict], - [Byte , EqDict], - [Char , EqDict], - [Unit , EqDict], - [Type , EqDict], - [SymbolT, EqDict], + [Int , eqDict], + [Bool , eqDict], + [Double , eqDict], + [Byte , eqDict], + [Char , eqDict], + [Unit , eqDict], + [Type , eqDict], + [SymbolT, eqDict], ]);