add enum type (generalization of sum-type)
This commit is contained in:
parent
8653bb99c6
commit
0b262daf7f
6 changed files with 102 additions and 7 deletions
|
|
@ -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
88
structures/enum.js
Normal 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)));
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue