simplify 'enum'

This commit is contained in:
Joeri Exelmans 2025-06-03 10:42:36 +02:00
parent 366b1ec4e0
commit aee8d5b5e1
6 changed files with 113 additions and 59 deletions

3
README.txt Normal file
View file

@ -0,0 +1,3 @@
Browser and NodeJS compatible library.
Tests require NodeJS.

View file

@ -1,6 +1,4 @@
import { capitalizeFirstLetter } from "../util/util.js"; import { capitalizeFirstLetter } from "../util/util.js";
import { newProduct as newProduct, getLeft } from "./product.js";
import { newLeft, newRight, match } from "./sum.js";
const eatParameters = (numParams, result) => { const eatParameters = (numParams, result) => {
if (numParams === 0) { if (numParams === 0) {
@ -9,29 +7,38 @@ const eatParameters = (numParams, result) => {
else return () => eatParameters(numParams-1, result); else return () => eatParameters(numParams-1, result);
} }
export const makeMatchFn = variants => { export const makeMatchFn = variantNames => {
if (variants.length === 0) { if (variantNames.length === 0) {
throw new Error("Bottom!"); return _ => {
throw new Error("Bottom!");
};
} }
const [_, ...remainingVariants] = variants; return enumm => {
return sum => handler => { return matchFn(enumm, variantNames);
return match(sum) }
(leftValue => eatParameters(remainingVariants.length, handler(leftValue))) };
(rightValue => makeMatchFn(remainingVariants)(rightValue));
const matchFn = (enumm, variantNames) => {
const [variantName, ...remaining] = variantNames;
return handler => {
if (enumm.variant === variantName) {
return eatParameters(
remaining.length,
handler(enumm.value));
}
else {
return matchFn(enumm, remaining);
}
}; };
}; };
export const makeConstructors = variants => { export const makeConstructors = variantNames => {
if (variants.length === 0) { if (new Set(variantNames).size !== variantNames.length) {
return []; throw new Error("precondition failed: non-unique variant names");
} }
const [variant, ...remainingVariants] = variants; return variantNames.map(variantName => {
const name = getLeft(variant); const ctorName = `new${capitalizeFirstLetter(variantName)}`;
const ctorName = `new${capitalizeFirstLetter(name)}`; const ctor = { [ctorName]: value => ({variant: variantName, value}) }[ctorName];
const constructor = { [ctorName]: val => newLeft(val) }[ctorName]; return ctor;
return [ })
constructor,
...makeConstructors(remainingVariants).map(ctor =>
({[ctor.name]: val => newRight(ctor(val))}[ctor.name])),
];
}; };

View file

@ -1,10 +1,9 @@
import { makeCompareFn } from "../compare/dynamic.js";
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
import { newDynamic } from "../primitives/dynamic.js"; import { newDynamic } from "../primitives/dynamic.js";
import { Bottom } from "../primitives/primitive_types.js"; import { zip } from "../util/util.js";
import { makeConstructors, makeMatchFn } from "./enum.js"; import { makeConstructors, makeMatchFn } from "./enum.js";
import { getRight } from "./product.js"; import { getLeft, getRight } from "./product.js";
import { fnType, sumType } from "./type_constructors.types.js"; import { fnType } from "./type_constructors.types.js";
// 'variants' is an array of (name: string, type: Type) pairs. // 'variants' is an array of (name: string, type: Type) pairs.
// e.g., the list of variants: // e.g., the list of variants:
@ -14,39 +13,39 @@ import { fnType, sumType } from "./type_constructors.types.js";
// results in the type: // results in the type:
// (Int | ([Int] | (Unit | ⊥))) // (Int | ([Int] | (Unit | ⊥)))
const _enumType = rootSelf => variants => { // const _enumType = rootSelf => variants => {
// console.log("enumType..", variants); // // console.log("enumType..", variants);
if (variants.length === 0) { // if (variants.length === 0) {
return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated) // return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated)
} // }
const [variant, ...rest] = variants; // const [variant, ...rest] = variants;
const variantType = getRight(variant); // const variantType = getRight(variant);
return sumType // return sumType
(self => { // (self => {
// console.log("requested left type (of enumType)") // // console.log("requested left type (of enumType)")
return variantType(rootSelf || self); // return variantType(rootSelf || self);
}) // })
(self => { // (self => {
// console.log("requested right type (of enumType)") // // console.log("requested right type (of enumType)")
return _enumType(self)(rest) // return _enumType(self)(rest)
}); // });
}; // };
export const enumType = _enumType(null); // export const enumType = _enumType(null);
export const makeConstructorTypes = type => variants => { export const makeConstructorTypes = type => variants => {
return variants.map(variant => { return variants.map(variant => {
const variantType = getRight(variant); const variantType = getRight(variant);
return fnType(_ => variantType)(_ => type); return fnType(_ => variantType)(_ => type);
}); });
} };
export const makeMatchFnType = type => variants => { export const makeMatchFnType = type => variants => {
return makeGeneric(resultType => return makeGeneric(a =>
fnType fnType
(_ => type) (_ => type)
(_ => handlerType(resultType)(type)(variants))); (_ => handlerType(a)(type)(variants)));
} };
const handlerType = resultType => type => variants => { const handlerType = resultType => type => variants => {
if (variants.length === 0) { if (variants.length === 0) {
@ -57,25 +56,24 @@ const handlerType = resultType => type => variants => {
return fnType return fnType
(_ => fnType(_ => variantType)(_ => resultType)), // handler (_ => fnType(_ => variantType)(_ => resultType)), // handler
(_ => handlerType(resultType)(type)(rest)); // rest (_ => handlerType(resultType)(type)(rest)); // rest
} };
export const makeModuleEnum = type => variants => { export const makeModuleEnum = type => variants => {
const ctors = makeConstructors(variants); const variantNames = variants.map(getLeft);
const ctors = makeConstructors(variantNames);
const ctorTypes = makeConstructorTypes(type)(variants); const ctorTypes = makeConstructorTypes(type)(variants);
const matchFn = makeMatchFn(variants); const matchFn = makeMatchFn(variantNames);
const matchFnType = makeMatchFnType(type)(variants); const matchFnType = makeMatchFnType(type)(variants);
const module = [ const module = [
// ["type", newDynamic(type)(Type)],
// constructors: // constructors:
...zip(ctors, ctorTypes) ...zip(ctors, ctorTypes)
.map(([ctor, ctorType]) => ["ctor", newDynamic(ctor)(ctorType)]), .map(([ctor, ctorType]) => ["ctor", newDynamic(ctor)(ctorType)]),
// match-function: // match-function:
newDynamic(matchFn, matchFnType), ["match", newDynamic(matchFn)(matchFnType)],
// compare-function: // // compare-function:
newDynamic(makeCompareFn(enumType(variants))) // newDynamic(makeCompareFn(enumType(variants)))
]; ];
return module; return module;
} };

View file

@ -4,6 +4,6 @@
// Product-types of more fields (called Structs) can be constructed by nesting Product-types. // Product-types of more fields (called Structs) can be constructed by nesting Product-types.
// In JS, all products are encoded in the same way: // In JS, all products are encoded in the same way:
export const newProduct = l => r => ({l, r}); export const newProduct = l => r => ({l, r}); // <-- apparently, this object-based encoding is faster than array-based, in both FF and Chrome.
export const getLeft = product => product.l; export const getLeft = product => product.l;
export const getRight = product => product.r; export const getRight = product => product.r;

46
tests/enum.js Normal file
View file

@ -0,0 +1,46 @@
import { makeTypeConstructor } from "../lib/meta/type_constructor.js";
import { Char, Double, Unit } from "../lib/primitives/primitive_types.js";
import { unit } from "../lib/primitives/unit.js";
import { makeModuleEnum } from "../lib/structures/enum.types.js";
import { newProduct } from "../lib/structures/product.js";
import { lsType } from "../lib/structures/type_constructors.types.js";
import assert from "node:assert";
const variants = [
newProduct("fever")(_ => Double),
newProduct("confused")(_ => Unit),
newProduct("other")(_ => lsType(_ => Char)),
];
const symbolSymptom = "Symptom__c0442736bb6f11ae99a51b14e4db44b4";
const Symptom = makeTypeConstructor(symbolSymptom)(0);
const [
// constructors
[, {i: newFever}],
[, {i: newConfused}],
[, {i: newOther}],
// match function
[, {i: matchSymptom}],
] = makeModuleEnum(Symptom)(variants);
const fever = newFever(38.5);
const confused = newConfused(unit);
const other = newOther("sweating");
console.log("observe the encoding of different variant instances:");
console.log(" ", fever);
console.log(" ", confused);
console.log(" ", other);
const description = symptom => matchSymptom(symptom)
(fever => `Fever (${fever} degrees)`)
(_confused => `Confused`)
(other => `Other: ${other}`);
assert.strictEqual("Fever (38.5 degrees)", description(fever));
assert.strictEqual("Confused" , description(confused));
assert.strictEqual("Other: sweating" , description(other));

View file

@ -5,7 +5,7 @@ import { Bool, Char, Int } from "../lib/primitives/primitive_types.js";
import { makeModuleStruct } from "../lib/structures/struct.types.js"; import { makeModuleStruct } from "../lib/structures/struct.types.js";
import { lsType } from "../lib/structures/type_constructors.types.js"; import { lsType } from "../lib/structures/type_constructors.types.js";
// Nominal type
const symbolPerson = "Person__22a59ca589b4a7efdbe20b52f380e50f"; const symbolPerson = "Person__22a59ca589b4a7efdbe20b52f380e50f";
const Person = makeTypeConstructor(symbolPerson)(0); const Person = makeTypeConstructor(symbolPerson)(0);