restructure code a bit, add comparison functions for primitive types and composed types (needed to put values in sets)

This commit is contained in:
Joeri Exelmans 2025-04-17 15:11:06 +02:00
parent 3978f7f835
commit 8653bb99c6
12 changed files with 175 additions and 64 deletions

29
compare/primitives.js Normal file
View file

@ -0,0 +1,29 @@
import { Char, Double, Int } from "../primitives/types.js";
export const compareNumbers = x => y => {
if (typeof(x) !== 'number' || typeof(y) !== 'number') {
throw new Error(`was only meant to compare numbers! got ${x} and ${y}`);
}
return (x < y) ? -1 : (x > y) ? 1 : 0;
}
export const compareStrings = x => y => {
if (typeof(x) !== 'string' || typeof(y) !== 'string') {
throw new Error(`was only meant to compare strings! got ${x} and ${y}`);
}
return (x < y) ? -1 : (x > y) ? 1 : 0;
}
export const compareBools = x => y => {
if (typeof(x) !== 'boolean' || typeof(y) !== 'boolean') {
throw new Error(`was only meant to compare booleans! got ${x} and ${y}`);
}
return x - y;
};
export const typeToCmp = new Map([
[Int, compareNumbers],
[Char, compareStrings],
[Double, compareNumbers],
[Boolean, compareBools],
]);

58
compare/structures.js Normal file
View file

@ -0,0 +1,58 @@
import { compareNumbers } from "./primitives.js"
import { length as lengthLs } from "../structures/list.js";
import { read, length as lengthSet } from "../structures/set.js";
import { constructorProduct, getLeft, getRight } from "../structures/product.js";
import { match } from "../structures/sum.js";
// (a -> a -> Int) -> [a] -> [a] -> Int
export const compareLists = compareElems => x => y => {
return compareNumbers(lengthLs(x))(lengthLs(y))
|| (() => {
for (let i=0; i<lengthLs(x); i++) {
const elemCmp = compareElems(get(x)(i))(get(y)(i));
if (elemCmp !== 0) {
return elemCmp;
}
}
return 0;
})();
};
// (a -> a -> Int) -> (b -> b -> Int) -> (a, b) -> (a, b) -> Int
export const compareProducts = compareLeft => compareRight => x => y => {
return compareLeft (getLeft (x))(getLeft (y))
|| compareRight(getRight(x))(getRight(y));
};
// (a -> a -> Int) -> (b -> b -> Int) -> (a | b) -> (a | b) -> Int
export const compareSums = compareLeft => compareRight => x => y => {
return match(x)(constructorProduct
(leftValueX => match(y)(constructorProduct
(leftValueY => compareLeft(leftValueX)(leftValueY))
((rightValueY) => -1) // x is 'left' and y is 'right' => x < y
))
(rightValueX => match(y)(constructorProduct
(leftValueY => 1) // x is 'right' and y is 'left' => x > y
(rightValueY => compareRight(rightValueX)(rightValueY))
))
);
};
// (a -> a -> Int) -> {a} -> {a} -> Int
export const compareSets = compareElems => x => y => {
return compareNumbers(lengthSet(x))(lengthSet(y))
|| (() => {
// sets have same size -> iterate over both sets and compare their elements pairwise
// because of the underlying red-black tree, iteration happens in ordered fashion
const iterate = iterX => iterY =>
read(iterX)
(keyX => nextX =>
read(iterY)
// we could also use the comparison function that is embedded in the set object,
// but to be consistent with the other comparison-functions, we don't.
(keyY => nextY => compareElems(keyX)(keyY) || iterate(nextX)(nextY))
(0)) // end of set y (we'll never get here because sets are same size)
(0) // end of set x;
iterate(first(x))(first(y));
})();
};

31
compare/versioning.js Normal file
View file

@ -0,0 +1,31 @@
export const compareSlots = compareElems => slotA => slotB => {
if (slotA.depth < slotB.depth) {
return -1;
}
if (slotB.depth < slotA.depth) {
return 1;
}
return compareValues(compareElems)(slotA.value)(slotB.value);
};
export const compareValues = compareElems => valA => valB => {
if (valA.kind < valB.kind) {
return -1;
}
if (valB.kind < valA.kind) {
return 1;
}
if (valA.kind === "literal") {
return compareElems(valA.out)(valB.out);
}
if (valA.kind === "read") {
return compareSlots(compareElems)(valA.slot)(valB.slot);
}
if (valA.kind === "transformation") {
const cmpIn = compareValues(compareElems)(valA.in)(valB.in);
if (cmpIn !== 0) {
return cmpIn;
}
return compareValues(compareElems)(valA.fn)(valB.fn);
}
};

View file

@ -43,5 +43,3 @@ export const ModuleSymbols = {l:[
{i: SymbolType , t: SymbolT}, {i: SymbolType , t: SymbolT},
{i: SymbolGenericType, t: SymbolT}, {i: SymbolGenericType, t: SymbolT},
]}; ]};

View file

@ -3,6 +3,7 @@ import { newLiteral, transform, read, getReadDependencies, verifyValue } from ".
import { merge, merge2, newSlot, overwrite } from "../versioning/slot.js"; import { merge, merge2, newSlot, overwrite } from "../versioning/slot.js";
import createRBTree from "functional-red-black-tree"; import createRBTree from "functional-red-black-tree";
import { add, emptySet, RBTreeWrapper } from "../structures/set.js"; import { add, emptySet, RBTreeWrapper } from "../structures/set.js";
import { compareNumbers } from "../compare/primitives.js";
const inc = x => x + 1; const inc = x => x + 1;
@ -58,14 +59,8 @@ const sixSlot = overwrite(fiveSlot)(newLiteral(6));
const sevenSlot = overwrite(fiveSlot)(newLiteral(7)); const sevenSlot = overwrite(fiveSlot)(newLiteral(7));
const eightSlot = overwrite(fiveSlot)(newLiteral(8)); const eightSlot = overwrite(fiveSlot)(newLiteral(8));
const numCompare = x => y => { const intMerge = merge(compareNumbers);
if (typeof(x) !== 'number' || typeof(y) !== 'number') { const intMerge2 = merge2(compareNumbers);
throw new Error(`was only meant to compare numbers! got ${x} and ${y}`);
}
return (x < y) ? -1 : (x > y) ? 1 : 0;
};
const intMerge = merge(numCompare);
const intMerge2 = merge2(numCompare);
const sixSevenSlot = intMerge(sixSlot)(sevenSlot); const sixSevenSlot = intMerge(sixSlot)(sevenSlot);
const sevenEightSlot = intMerge(sevenSlot)(eightSlot); const sevenEightSlot = intMerge(sevenSlot)(eightSlot);
@ -83,13 +78,6 @@ console.log(pretty({sixSevenEightSlot}));
// console.log("## Heterogeneous data ##"); // console.log("## Heterogeneous data ##");
// console.log("########################"); // console.log("########################");
// const strCompare = x => y => {
// if (typeof(x) !== 'string' || typeof(y) !== 'string') {
// throw new Error(`was only meant to compare strings! got ${x} and ${y}`);
// }
// return (x < y) ? -1 : (x > y) ? 1 : 0;
// };
// // Slot<Int> // // Slot<Int>
// const numberOfSheepSlot = newSlot(Symbol('numberOfSheep'))(newLiteral(5)); // const numberOfSheepSlot = newSlot(Symbol('numberOfSheep'))(newLiteral(5));
// const alternativeNumberOfSheepSlot = newSlot(Symbol('alternativeNumberOfSheep'))(newLiteral(6)); // const alternativeNumberOfSheepSlot = newSlot(Symbol('alternativeNumberOfSheep'))(newLiteral(6));
@ -108,7 +96,7 @@ console.log(pretty({sixSevenEightSlot}));
// transform(read(labelSlot))(combineFn))); // transform(read(labelSlot))(combineFn)));
// console.log( // console.log(
// add(add(emptySet(compareSlots(strCompare)))(labelAndValueSlotA))(labelAndValueSlotB) // add(add(emptySet(compareSlots(compareStrings)))(labelAndValueSlotA))(labelAndValueSlotB)
// ); // );
// merge()(labelSlot)(labelAndValueSlot) // merge()(labelSlot)(labelAndValueSlot)
@ -119,4 +107,9 @@ console.log("## RB Tree ##")
console.log("#############") console.log("#############")
// just a small experiment // just a small experiment
console.log(new RBTreeWrapper(createRBTree().insert(1).insert(1).insert(2))); console.log(
createRBTree()
.insert(1)
.insert(1)
.insert(2)
);

View file

@ -6,12 +6,20 @@ import { lsType } from "./types.js";
import { Typed } from "../typed.js" import { Typed } from "../typed.js"
// 'normal' implementation // 'normal' implementation
const emptyList = {l:[]}; export const emptyList = {l:[]};
const emptyListType = makeGeneric(a => lsType(a)); const emptyListType = makeGeneric(a => lsType(a));
const get = ls => i => ls.l[i]; export const get = ls => i => ls.l[i];
const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)}); export const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)});
const push = ls => elem => ({l:ls.l.concat([elem])}); export const push = ls => elem => ({l:ls.l.concat([elem])});
const map = ls => fn => ({ l: ls.l.map(elem => fn(elem)) }); export const map = ls => fn => ({ l: ls.l.map(elem => fn(elem)) });
export const length = ls => ls.l.length;
export const fold = ls => callback => initial => {
let acc = initial;
for (let i=0; i<ls.l.length; i++) {
acc = callback(acc)(ls.l[i]);
}
return acc;
}
export const String = lsType(Char); // alias export const String = lsType(Char); // alias
export const Module = lsType(Typed); export const Module = lsType(Typed);

View file

@ -1,3 +1,7 @@
// Product-type (also called: pair, tuple)
// 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.
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
import { GenericType, Type } from "../primitives/types.js"; import { GenericType, Type } from "../primitives/types.js";
import { typedFnType } from "./types.js"; import { typedFnType } from "./types.js";

View file

@ -24,6 +24,20 @@ export const emptySet = compareFn => new RBTreeWrapper(createRBTree((x, y) => co
export const has = set => key => set.tree.get(key) === true; export const has = set => key => set.tree.get(key) === true;
export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWrapper(set.tree.insert(key, true)); export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWrapper(set.tree.insert(key, true));
export const remove = set => key => new RBTreeWrapper(set.tree.remove(key)); export const remove = set => key => new RBTreeWrapper(set.tree.remove(key));
export const length = set => set.tree.length;
export const first = set => set.tree.begin;
export const last = set => set.tree.end;
// test if iterator is 'done', and if not, get element and advance iterator.
export const read = iter => ifNotDone => ifDone => {
if (iter !== undefined && iter.valid) {
return ifNotDone(iter.key)(iter.clone().next());
}
else {
return ifDone();
}
};
export const forEach = set => fn => { export const forEach = set => fn => {
set.tree.forEach(key => { fn(key); }); set.tree.forEach(key => { fn(key); });

View file

@ -7,7 +7,10 @@ function capitalizeFirstLetter(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1); return String(val).charAt(0).toUpperCase() + String(val).slice(1);
} }
// [{l: "x", r: Double}, {l: "y", r: Double}] => (Double × (Double × Unit)) // 'fields' is an array of (name: string, type: Type) pairs.
// e.g.:
// [{l: "x", r: Double}, {l: "y", r: Double}]
// results in the type (Double × (Double × Unit))
export const structType = fields => { export const structType = fields => {
if (fields.length === 0) { if (fields.length === 0) {
return Unit; return Unit;

View file

@ -1,17 +1,22 @@
// Sum-type (also called: tagged union, disjoin union, variant type)
// A Sum-type always has only two variants, called "left" and "right".
// Sum-types of more variants (called Enums) can be constructed by nesting Sum-types.
import { prodType } from "./types.js"; import { prodType } from "./types.js";
import { GenericType, Type } from "../primitives/types.js"; import { GenericType, Type } from "../primitives/types.js";
import { typedFnType } from "./types.js"; import { typedFnType } from "./types.js";
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
import { sumType } from "./types.js"; import { sumType } from "./types.js";
export const constructorLeft = left => ({variant: "L", value: left }); export const constructorLeft = left => ({t: "L", v: left }); // 't': tag, 'v': value
export const constructorRight = right => ({variant: "R", value: right}); export const constructorRight = right => ({t: "R", v: right});
// signature: // signature:
// sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType // sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType
export const match = sum => handlers => sum.variant === "L" export const match = sum => handlers =>
? handlers.left(sum.value) sum.t === "L"
: handlers.right(sum.value); ? handlers.left(sum.v)
: handlers.right(sum.v);
export const ModuleSum = {l:[ export const ModuleSum = {l:[
// binary type constructor // binary type constructor

View file

@ -1,6 +1,7 @@
import { add, emptySet, forEach } from "../structures/set.js"; import { add, emptySet, forEach } from "../structures/set.js";
import { deepEqual } from "../util/util.js"; import { deepEqual } from "../util/util.js";
import {inspect} from "node:util"; import {inspect} from "node:util";
import { compareSlots } from "../compare/versioning.js";
// UUID -> Value<a> -> Slot<a> // UUID -> Value<a> -> Slot<a>
export const newSlot = uuid => value => ({ export const newSlot = uuid => value => ({
@ -10,39 +11,6 @@ export const newSlot = uuid => value => ({
[inspect.custom]: (depth, options, inspect) => `newSlot(${inspect(uuid)}, ${inspect(value)})`, [inspect.custom]: (depth, options, inspect) => `newSlot(${inspect(uuid)}, ${inspect(value)})`,
}); });
export const compareSlots = compareElems => slotA => slotB => {
if (slotA.depth < slotB.depth) {
return -1;
}
if (slotB.depth < slotA.depth) {
return 1;
}
return compareValues(compareElems)(slotA.value)(slotB.value);
};
export const compareValues = compareElems => valA => valB => {
if (valA.kind < valB.kind) {
return -1;
}
if (valB.kind < valA.kind) {
return 1;
}
if (valA.kind === "literal") {
return compareElems(valA.out)(valB.out);
}
if (valA.kind === "read") {
return compareSlots(compareElems)(valA.slot)(valB.slot);
}
if (valA.kind === "transformation") {
const cmpIn = compareValues(compareElems)(valA.in)(valB.in);
if (cmpIn !== 0) {
return cmpIn;
}
return compareValues(compareElems)(valA.fn)(valB.fn);
}
};
// Slot<a> -> Value<a> -> Slot<a> // Slot<a> -> Value<a> -> Slot<a>
export const overwrite = slot => value => ({ export const overwrite = slot => value => ({
overwrites: slot, overwrites: slot,