From aee8d5b5e115df8bcb9967583a062b3399a0e9c9 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Tue, 3 Jun 2025 10:42:36 +0200 Subject: [PATCH] simplify 'enum' --- README.txt | 3 ++ lib/structures/enum.js | 51 +++++++++++++++------------ lib/structures/enum.types.js | 68 +++++++++++++++++------------------- lib/structures/product.js | 2 +- tests/enum.js | 46 ++++++++++++++++++++++++ tests/struct.js | 2 +- 6 files changed, 113 insertions(+), 59 deletions(-) create mode 100644 README.txt create mode 100644 tests/enum.js diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..0173a7a --- /dev/null +++ b/README.txt @@ -0,0 +1,3 @@ +Browser and NodeJS compatible library. + +Tests require NodeJS. \ No newline at end of file diff --git a/lib/structures/enum.js b/lib/structures/enum.js index 0af542b..99b59fb 100644 --- a/lib/structures/enum.js +++ b/lib/structures/enum.js @@ -1,6 +1,4 @@ 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) => { if (numParams === 0) { @@ -9,29 +7,38 @@ const eatParameters = (numParams, result) => { else return () => eatParameters(numParams-1, result); } -export const makeMatchFn = variants => { - if (variants.length === 0) { - throw new Error("Bottom!"); +export const makeMatchFn = variantNames => { + if (variantNames.length === 0) { + return _ => { + throw new Error("Bottom!"); + }; } - const [_, ...remainingVariants] = variants; - return sum => handler => { - return match(sum) - (leftValue => eatParameters(remainingVariants.length, handler(leftValue))) - (rightValue => makeMatchFn(remainingVariants)(rightValue)); + return enumm => { + return matchFn(enumm, variantNames); + } +}; + +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 => { - if (variants.length === 0) { - return []; +export const makeConstructors = variantNames => { + if (new Set(variantNames).size !== variantNames.length) { + throw new Error("precondition failed: non-unique variant names"); } - const [variant, ...remainingVariants] = variants; - const name = getLeft(variant); - const ctorName = `new${capitalizeFirstLetter(name)}`; - const constructor = { [ctorName]: val => newLeft(val) }[ctorName]; - return [ - constructor, - ...makeConstructors(remainingVariants).map(ctor => - ({[ctor.name]: val => newRight(ctor(val))}[ctor.name])), - ]; + return variantNames.map(variantName => { + const ctorName = `new${capitalizeFirstLetter(variantName)}`; + const ctor = { [ctorName]: value => ({variant: variantName, value}) }[ctorName]; + return ctor; + }) }; diff --git a/lib/structures/enum.types.js b/lib/structures/enum.types.js index 2c999b7..8dad465 100644 --- a/lib/structures/enum.types.js +++ b/lib/structures/enum.types.js @@ -1,10 +1,9 @@ -import { makeCompareFn } from "../compare/dynamic.js"; import { makeGeneric } from "../generics/generics.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 { getRight } from "./product.js"; -import { fnType, sumType } from "./type_constructors.types.js"; +import { getLeft, getRight } from "./product.js"; +import { fnType } from "./type_constructors.types.js"; // 'variants' is an array of (name: string, type: Type) pairs. // e.g., the list of variants: @@ -14,39 +13,39 @@ import { fnType, sumType } from "./type_constructors.types.js"; // results in the type: // (Int | ([Int] | (Unit | ⊥))) -const _enumType = rootSelf => variants => { - // console.log("enumType..", variants); - if (variants.length === 0) { - return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated) - } - const [variant, ...rest] = variants; - const variantType = getRight(variant); - return sumType - (self => { - // console.log("requested left type (of enumType)") - return variantType(rootSelf || self); - }) - (self => { - // console.log("requested right type (of enumType)") - return _enumType(self)(rest) - }); -}; +// const _enumType = rootSelf => variants => { +// // console.log("enumType..", variants); +// if (variants.length === 0) { +// return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated) +// } +// const [variant, ...rest] = variants; +// const variantType = getRight(variant); +// return sumType +// (self => { +// // console.log("requested left type (of enumType)") +// return variantType(rootSelf || self); +// }) +// (self => { +// // console.log("requested right type (of enumType)") +// return _enumType(self)(rest) +// }); +// }; -export const enumType = _enumType(null); +// export const enumType = _enumType(null); export const makeConstructorTypes = type => variants => { return variants.map(variant => { const variantType = getRight(variant); return fnType(_ => variantType)(_ => type); }); -} +}; export const makeMatchFnType = type => variants => { - return makeGeneric(resultType => + return makeGeneric(a => fnType (_ => type) - (_ => handlerType(resultType)(type)(variants))); -} + (_ => handlerType(a)(type)(variants))); +}; const handlerType = resultType => type => variants => { if (variants.length === 0) { @@ -57,25 +56,24 @@ const handlerType = resultType => type => variants => { return fnType (_ => fnType(_ => variantType)(_ => resultType)), // handler (_ => handlerType(resultType)(type)(rest)); // rest -} +}; export const makeModuleEnum = type => variants => { - const ctors = makeConstructors(variants); + const variantNames = variants.map(getLeft); + const ctors = makeConstructors(variantNames); const ctorTypes = makeConstructorTypes(type)(variants); - const matchFn = makeMatchFn(variants); + const matchFn = makeMatchFn(variantNames); const matchFnType = makeMatchFnType(type)(variants); const module = [ - // ["type", newDynamic(type)(Type)], - // constructors: ...zip(ctors, ctorTypes) .map(([ctor, ctorType]) => ["ctor", newDynamic(ctor)(ctorType)]), // match-function: - newDynamic(matchFn, matchFnType), + ["match", newDynamic(matchFn)(matchFnType)], - // compare-function: - newDynamic(makeCompareFn(enumType(variants))) + // // compare-function: + // newDynamic(makeCompareFn(enumType(variants))) ]; return module; -} \ No newline at end of file +}; diff --git a/lib/structures/product.js b/lib/structures/product.js index e39c444..72d0b52 100644 --- a/lib/structures/product.js +++ b/lib/structures/product.js @@ -4,6 +4,6 @@ // Product-types of more fields (called Structs) can be constructed by nesting Product-types. // 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 getRight = product => product.r; diff --git a/tests/enum.js b/tests/enum.js new file mode 100644 index 0000000..8a8a71b --- /dev/null +++ b/tests/enum.js @@ -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)); diff --git a/tests/struct.js b/tests/struct.js index 998e577..085c3c4 100644 --- a/tests/struct.js +++ b/tests/struct.js @@ -5,7 +5,7 @@ import { Bool, Char, Int } from "../lib/primitives/primitive_types.js"; import { makeModuleStruct } from "../lib/structures/struct.types.js"; import { lsType } from "../lib/structures/type_constructors.types.js"; - +// Nominal type const symbolPerson = "Person__22a59ca589b4a7efdbe20b52f380e50f"; const Person = makeTypeConstructor(symbolPerson)(0);