simplify 'enum'
This commit is contained in:
parent
366b1ec4e0
commit
aee8d5b5e1
6 changed files with 113 additions and 59 deletions
3
README.txt
Normal file
3
README.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
Browser and NodeJS compatible library.
|
||||||
|
|
||||||
|
Tests require NodeJS.
|
||||||
|
|
@ -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])),
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
46
tests/enum.js
Normal 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));
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue