diff --git a/compare/primitives.js b/compare/primitives.js new file mode 100644 index 0000000..293c73f --- /dev/null +++ b/compare/primitives.js @@ -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], +]); diff --git a/compare/structures.js b/compare/structures.js new file mode 100644 index 0000000..c71ee0f --- /dev/null +++ b/compare/structures.js @@ -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 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)); + })(); +}; diff --git a/compare/versioning.js b/compare/versioning.js new file mode 100644 index 0000000..b546655 --- /dev/null +++ b/compare/versioning.js @@ -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); + } +}; diff --git a/primitives/types.js b/primitives/types.js index 5f12473..2017940 100644 --- a/primitives/types.js +++ b/primitives/types.js @@ -43,5 +43,3 @@ export const ModuleSymbols = {l:[ {i: SymbolType , t: SymbolT}, {i: SymbolGenericType, t: SymbolT}, ]}; - - diff --git a/scripts/versioning.js b/scripts/versioning.js index aa0bd1e..70e63b8 100644 --- a/scripts/versioning.js +++ b/scripts/versioning.js @@ -3,6 +3,7 @@ import { newLiteral, transform, read, getReadDependencies, verifyValue } from ". import { merge, merge2, newSlot, overwrite } from "../versioning/slot.js"; import createRBTree from "functional-red-black-tree"; import { add, emptySet, RBTreeWrapper } from "../structures/set.js"; +import { compareNumbers } from "../compare/primitives.js"; const inc = x => x + 1; @@ -58,14 +59,8 @@ const sixSlot = overwrite(fiveSlot)(newLiteral(6)); const sevenSlot = overwrite(fiveSlot)(newLiteral(7)); const eightSlot = overwrite(fiveSlot)(newLiteral(8)); -const numCompare = 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; -}; -const intMerge = merge(numCompare); -const intMerge2 = merge2(numCompare); +const intMerge = merge(compareNumbers); +const intMerge2 = merge2(compareNumbers); const sixSevenSlot = intMerge(sixSlot)(sevenSlot); const sevenEightSlot = intMerge(sevenSlot)(eightSlot); @@ -83,13 +78,6 @@ console.log(pretty({sixSevenEightSlot})); // console.log("## Heterogeneous data ##"); // 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 // const numberOfSheepSlot = newSlot(Symbol('numberOfSheep'))(newLiteral(5)); // const alternativeNumberOfSheepSlot = newSlot(Symbol('alternativeNumberOfSheep'))(newLiteral(6)); @@ -108,7 +96,7 @@ console.log(pretty({sixSevenEightSlot})); // transform(read(labelSlot))(combineFn))); // console.log( -// add(add(emptySet(compareSlots(strCompare)))(labelAndValueSlotA))(labelAndValueSlotB) +// add(add(emptySet(compareSlots(compareStrings)))(labelAndValueSlotA))(labelAndValueSlotB) // ); // merge()(labelSlot)(labelAndValueSlot) @@ -119,4 +107,9 @@ console.log("## RB Tree ##") console.log("#############") // just a small experiment -console.log(new RBTreeWrapper(createRBTree().insert(1).insert(1).insert(2))); \ No newline at end of file +console.log( + createRBTree() + .insert(1) + .insert(1) + .insert(2) +); diff --git a/structures/list.js b/structures/list.js index eab85f0..d06eca5 100644 --- a/structures/list.js +++ b/structures/list.js @@ -6,12 +6,20 @@ import { lsType } from "./types.js"; import { Typed } from "../typed.js" // 'normal' implementation -const emptyList = {l:[]}; +export const emptyList = {l:[]}; const emptyListType = makeGeneric(a => lsType(a)); -const get = ls => i => ls.l[i]; -const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)}); -const push = ls => elem => ({l:ls.l.concat([elem])}); -const map = ls => fn => ({ l: ls.l.map(elem => fn(elem)) }); +export const get = ls => i => ls.l[i]; +export const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)}); +export const push = ls => elem => ({l:ls.l.concat([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 new RBTreeWrapper(createRBTree((x, y) => co 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 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 => { set.tree.forEach(key => { fn(key); }); diff --git a/structures/struct.js b/structures/struct.js index aaeb588..dd91db4 100644 --- a/structures/struct.js +++ b/structures/struct.js @@ -7,7 +7,10 @@ function capitalizeFirstLetter(val) { 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 => { if (fields.length === 0) { return Unit; diff --git a/structures/sum.js b/structures/sum.js index e8136ca..bb02ff3 100644 --- a/structures/sum.js +++ b/structures/sum.js @@ -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 { GenericType, Type } from "../primitives/types.js"; import { typedFnType } from "./types.js"; import { makeGeneric } from "../generics/generics.js"; import { sumType } from "./types.js"; -export const constructorLeft = left => ({variant: "L", value: left }); -export const constructorRight = right => ({variant: "R", value: right}); +export const constructorLeft = left => ({t: "L", v: left }); // 't': tag, 'v': value +export const constructorRight = right => ({t: "R", v: right}); // signature: // sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType -export const match = sum => handlers => sum.variant === "L" - ? handlers.left(sum.value) - : handlers.right(sum.value); +export const match = sum => handlers => + sum.t === "L" + ? handlers.left(sum.v) + : handlers.right(sum.v); export const ModuleSum = {l:[ // binary type constructor diff --git a/versioning/slot.js b/versioning/slot.js index 587626b..163c90c 100644 --- a/versioning/slot.js +++ b/versioning/slot.js @@ -1,6 +1,7 @@ import { add, emptySet, forEach } from "../structures/set.js"; import { deepEqual } from "../util/util.js"; import {inspect} from "node:util"; +import { compareSlots } from "../compare/versioning.js"; // UUID -> Value -> Slot export const newSlot = uuid => value => ({ @@ -10,39 +11,6 @@ export const newSlot = uuid => 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 -> Value -> Slot export const overwrite = slot => value => ({ overwrites: slot, diff --git a/versioning/value.js b/versioning/value.js index 3709bc9..2184615 100644 --- a/versioning/value.js +++ b/versioning/value.js @@ -1,5 +1,5 @@ import { deepEqual } from "../util/util.js"; -import {inspect} from "node:util"; +import { inspect } from "node:util"; // A Value is either: // - a literal, without any dependencies.