add enum type (generalization of sum-type)

This commit is contained in:
Joeri Exelmans 2025-04-17 16:10:00 +02:00
parent 8653bb99c6
commit 0b262daf7f
6 changed files with 102 additions and 7 deletions

View file

@ -8,6 +8,7 @@ const SymbolDouble = Symbol('Double');
const SymbolByte = Symbol('Byte'); const SymbolByte = Symbol('Byte');
const SymbolChar = Symbol('Char'); const SymbolChar = Symbol('Char');
const SymbolUnit = Symbol('Unit'); const SymbolUnit = Symbol('Unit');
const SymbolBottom = Symbol('⊥');
const SymbolSymbol = Symbol('Symbol'); const SymbolSymbol = Symbol('Symbol');
const SymbolType = Symbol('Type'); const SymbolType = Symbol('Type');
const symbolAny = Symbol('Any'); const symbolAny = Symbol('Any');
@ -22,6 +23,9 @@ 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(SymbolUnit)(0); export const Unit = makeTypeConstructor(SymbolUnit)(0);
// Bottom type has no instances.
export const Bottom = makeTypeConstructor(SymbolBottom)(0);
export const SymbolT = makeTypeConstructor(SymbolSymbol)(0); export const SymbolT = makeTypeConstructor(SymbolSymbol)(0);
// Types are typed by Any // Types are typed by Any
@ -39,6 +43,7 @@ export const ModuleSymbols = {l:[
{i: SymbolByte , t: SymbolT}, {i: SymbolByte , t: SymbolT},
{i: SymbolChar , t: SymbolT}, {i: SymbolChar , t: SymbolT},
{i: SymbolUnit , t: SymbolT}, {i: SymbolUnit , t: SymbolT},
{i: SymbolBottom, t: SymbolT},
{i: SymbolSymbol, t: SymbolT}, {i: SymbolSymbol, t: SymbolT},
{i: SymbolType , t: SymbolT}, {i: SymbolType , t: SymbolT},
{i: SymbolGenericType, t: SymbolT}, {i: SymbolGenericType, t: SymbolT},

88
structures/enum.js Normal file
View file

@ -0,0 +1,88 @@
import { Bottom, Int, Unit } from "../primitives/types.js";
import { unit } from "../primitives/unit.js";
import { capitalizeFirstLetter } from "../util/util.js";
import { constructorProduct, getLeft, getRight } from "./product.js";
import { constructorLeft, constructorRight, match } from "./sum.js";
import { lsType, prettyT, sumType } from "./types.js";
// 'variants' is an array of (name: string, type: Type) pairs.
// e.g., the list of variants:
// [ { l: "price" , r: Int },
// { l: "prices" , r: [Int] },
// { l: "not_found", r: Unit } ]
// results in the type:
// (Int | ([Int] | (Unit | ⊥)))
export const 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(variantType)(enumType(rest));
};
const eatParameters = (numParams, result) => {
if (numParams === 0) {
return result;
}
else return () => eatParameters(numParams-1, result);
}
export const makeMatchFn = variants => {
if (variants.length === 0) {
return undefined;
}
const [_, ...remainingVariants] = variants;
return sum => handler => {
return (
match(sum)(constructorProduct
(leftValue => eatParameters(remainingVariants.length, handler(leftValue)))
(rightValue => makeMatchFn(remainingVariants)(rightValue))
));
};
};
export const makeConstructors = variants => {
if (variants.length === 0) {
return [];
}
const [variant, ...remainingVariants] = variants;
const name = getLeft(variant);
const ctorName = `new${capitalizeFirstLetter(name)}`;
const constructor = { [ctorName]: val => constructorLeft(val) }[ctorName];
return [
constructor,
...makeConstructors(remainingVariants).map(ctor =>
({[ctor.name]: val => constructorRight(ctor(val))}[ctor.name])),
];
}
///////////////////////////////////////////////////////////////////////////////
const variants = [
constructorProduct("price")(Int),
constructorProduct("prices")(lsType(Int)),
constructorProduct("not_found")(Unit),
];
const myEnum = enumType(variants);
console.log(prettyT(myEnum));
const [newPrice, newPrices, newNotFound] = makeConstructors(variants);
console.log(newPrice(10));
console.log(newPrices([20,30]));
console.log(newNotFound(unit));
const myMatchFn = makeMatchFn(variants);
const matchVariant = x => myMatchFn(x)
(price => `Price: ${price}`)
(prices => `Prices: ${prices}`)
(() => "Not found!");
console.log(matchVariant(newPrice(10)));
console.log(matchVariant(newPrices([20,30])));
console.log(matchVariant(newNotFound(unit)));

View file

@ -1,4 +1,5 @@
// Product-type (also called: pair, tuple) // Product-type (also called: pair, tuple)
// A Product-type always has only two fields, called "left" and "right". // A Product-type always has only two fields, called "left" and "right".
// 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.

View file

@ -1,12 +1,9 @@
import { Unit } from "../primitives/types.js"; import { Unit } from "../primitives/types.js";
import { unit } from "../primitives/unit.js"; import { unit } from "../primitives/unit.js";
import { capitalizeFirstLetter } from "../util/util.js";
import { constructorProduct, getLeft, getRight } from "./product.js"; import { constructorProduct, getLeft, getRight } from "./product.js";
import { fnType, prodType } from "./types.js"; import { fnType, prodType } from "./types.js";
function capitalizeFirstLetter(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
// 'fields' is an array of (name: string, type: Type) pairs. // 'fields' is an array of (name: string, type: Type) pairs.
// e.g.: // e.g.:
// [{l: "x", r: Double}, {l: "y", r: Double}] // [{l: "x", r: Double}, {l: "y", r: Double}]

View file

@ -15,8 +15,8 @@ export const constructorRight = right => ({t: "R", v: right});
// sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType // sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType
export const match = sum => handlers => export const match = sum => handlers =>
sum.t === "L" sum.t === "L"
? handlers.left(sum.v) ? handlers.l(sum.v)
: handlers.right(sum.v); : handlers.r(sum.v);
export const ModuleSum = {l:[ export const ModuleSum = {l:[
// binary type constructor // binary type constructor

View file

@ -42,4 +42,8 @@ export function zip(a, b) {
return a.map((k, i) => [k, b[i]]); return a.map((k, i) => [k, b[i]]);
} }
export function capitalizeFirstLetter(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}