diff --git a/lib/stdlib.js b/lib/stdlib.js
index b03fb0f..740cfe8 100644
--- a/lib/stdlib.js
+++ b/lib/stdlib.js
@@ -17,7 +17,9 @@ import { ModuleCompareTypes } from "./compare/type.types.js";
import { ModuleComparePrimitives } from "./compare/primitives.types.js";
import { ModuleCompareStructures } from "./compare/structures.types.js";
import { ModuleCompareDynamic } from "./compare/dynamic.types.js";
-import { ModuleVersioning } from "./versioning/types.js";
+import { ModuleVersioning } from "./versioning/value_slot.js";
+import { ModuleMerge } from "./versioning/merge.js";
+import { ModuleVersioningCompare } from "./versioning/compare.js";
export const ModuleStd = [
// Symbols (for nominal types)
@@ -51,4 +53,6 @@ export const ModuleStd = [
// Versioning
...ModuleVersioning,
+ ...ModuleMerge,
+ ...ModuleVersioningCompare,
];
diff --git a/lib/structures/set.js b/lib/structures/set.js
index 0dda8ef..33f0d9e 100644
--- a/lib/structures/set.js
+++ b/lib/structures/set.js
@@ -19,6 +19,12 @@ export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWr
export const remove = set => key => new RBTreeWrapper(set.tree.remove(key), inspectSet);
export const length = set => set.tree.length;
+export const union = setA => setB =>
+ fold
+ (acc => cur => add(acc)(cur))
+ (setA)
+ (setB);
+
export const fold = callback => initial => set => {
let acc = initial;
for (const iter=set.tree.begin; iter !== undefined && iter.valid; iter.next()) {
diff --git a/lib/versioning/compare.js b/lib/versioning/compare.js
new file mode 100644
index 0000000..c8deee3
--- /dev/null
+++ b/lib/versioning/compare.js
@@ -0,0 +1,50 @@
+import { compareDynamic } from "../compare/dynamic.js";
+import { compareSymbols, compareInts } from "../compare/primitives.js";
+import { makeTypeParser } from "../parser/type_parser.js";
+import { newDynamic } from "../primitives/dynamic.js";
+import { makeEnumCompareFn } from "../structures/enum.js";
+import { makeStructCompareFn } from "../structures/struct.js";
+import { Slot, Transformation, Value, Write } from "./value_slot.js";
+
+
+// comparison functions are mutually recursive...
+
+export const compareSlot = makeEnumCompareFn([
+ { l: "new", r: () => compareSymbols },
+ { l: "write", r: () => compareWrite },
+]);
+
+export const compareWrite = makeStructCompareFn([
+ { l: "slot", r: () => compareSlot },
+ { l: "value", r: () => compareValue },
+ { l: "depth", r: () => compareInts },
+]);
+
+export const compareTransformation = makeStructCompareFn([
+ { l: "input", r: () => compareValue },
+ { l: "fn", r: () => compareValue },
+ { l: "output", r: () => compareDynamic },
+]);
+
+export const compareValue = makeEnumCompareFn([
+ { l: "literal", r: () => compareDynamic },
+ { l: "read", r: () => compareWrite },
+ { l: "transform", r: () => compareTransformation },
+]);
+
+const mkType = makeTypeParser({
+ extraPrimitives: [
+ ["Value", Value],
+ ["Slot" , Slot ],
+ ["Write", Write],
+ ["Transformation", Transformation],
+ ],
+});
+
+export const ModuleVersioningCompare = [
+ // comparison
+ ["compareSlot" , newDynamic(compareSlot )(mkType("Slot -> Slot -> Ordering"))],
+ ["compareValue", newDynamic(compareValue)(mkType("Value -> Value -> Ordering"))],
+ ["compareWrite", newDynamic(compareWrite)(mkType("Write -> Write -> Ordering"))],
+ ["compareTransformation", newDynamic(compareTransformation)(mkType("Transformation -> Transformation -> Ordering"))],
+];
\ No newline at end of file
diff --git a/lib/versioning/merge.js b/lib/versioning/merge.js
new file mode 100644
index 0000000..7ef70ba
--- /dev/null
+++ b/lib/versioning/merge.js
@@ -0,0 +1,73 @@
+import { emptySet, add, length, forEach, union, has } from "../structures/set.js";
+import { getDepth, Slot, Value, Write } from "./value_slot.js";
+import { compareSlot } from "./compare.js";
+import { makeTypeParser } from "../parser/type_parser.js";
+import { newDynamic } from "../primitives/dynamic.js";
+
+const emptySetOfSlots = emptySet(compareSlot);
+
+export const findLCA = slotA => slotB => {
+ if (compareSlot(slotA)(slotB) === 0) {
+ return slotA;
+ }
+ if (getDepth(slotA) > getDepth(slotB)) {
+ // we can assume that slotA.variant === "write"
+ return findLCA(slotA.value.slot)(slotB);
+ }
+ else {
+ // we can assume that slotB.variant === "write"
+ return findLCA(slotA)(slotB.value.slot);
+ }
+};
+
+export const merge = slotA => slotB => {
+ const lca = findLCA(slotA)(slotB);
+ if (compareSlot(lca)(slotA) === 0) {
+ return add(emptySetOfSlots)(slotB);
+ }
+ if (compareSlot(lca)(slotB) === 0) {
+ return add(emptySetOfSlots)(slotA);
+ }
+ const setWithA = add(emptySetOfSlots)(slotA);
+ const setWithAandB = add(setWithA)(slotB);
+ return setWithAandB;
+};
+
+export const mergeN = slots => {
+ let toDelete = emptySetOfSlots;
+ forEach(slots)(slotA => {
+ forEach(slots)(slotB => {
+ // compare all non-identical pairs
+ if (compareSlot(slotA)(slotB) !== 0) {
+ const m = merge(slotA)(slotB);
+ if (length(m) === 1) {
+ // if in the pair, one is an ancestor of the other,
+ // only keep the other
+ if (has(m)(slotA)) {
+ toDelete = add(toDelete)(slotB);
+ }
+ }
+ }
+ });
+ });
+ let result = emptySetOfSlots;
+ forEach(slots)(slot => {
+ if (!has(toDelete)(slot)) {
+ result = add(result)(slot);
+ }
+ })
+ return result;
+};
+
+const mkType = makeTypeParser({
+ extraPrimitives: [
+ ["Value", Value],
+ ["Slot" , Slot ],
+ ["Write", Write],
+ ],
+});
+
+export const ModuleMerge = [
+ ["merge" , newDynamic(merge)(mkType("Slot -> Slot -> {Slot}"))],
+ ["mergeN", newDynamic(merge)(mkType("{Slot} -> {Slot}" ))],
+];
diff --git a/lib/versioning/slot.js b/lib/versioning/slot.js
deleted file mode 100644
index d5561b1..0000000
--- a/lib/versioning/slot.js
+++ /dev/null
@@ -1,68 +0,0 @@
-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";
-
-export const newSlot = uuid => ({
- kind: "new",
- uuid,
- depth: 0,
- [inspect.custom]: (depth, options, inspect) => `newSlot(${inspect(uuid)}, ${inspect(value)})`,
-});
-
-export const overwrite = slot => value => ({
- kind: "overwrite",
- overwrites: slot,
- value,
- depth: slot.depth + 1,
- // [inspect.custom]: (depth, options, inspect) => `overwrite(${inspect(slot)}, ${inspect(value)})`,
-});
-
-const slotsEqual = slotA => slotB => {
-
-}
-
-const findLCA = slotA => slotB => {
- if (slotA.depth === slotB.depth) {
-
- }
- if (deepEqual(slotA, slotB)) {
- return slotA;
- }
- if (slotA.depth > slotB.depth) {
- return findLCA(slotA.overwrites)(slotB)
- }
- else {
- return findLCA(slotB.overwrites)(slotA)
- }
-};
-
-export const merge = compareElems => slotA => slotB => {
- const lca = findLCA(slotA)(slotB);
- if (lca === undefined) {
- throw new Error("Could not find LCA");
- }
- if (deepEqual(lca, slotA)) {
- return add(emptySet(compareSlots(compareElems)))(slotB);
- // return new Set([slotB]); // B is successor of A -> fast-forward
- }
- if (deepEqual(lca, slotB)) {
- return add(emptySet(compareSlots(compareElems)))(slotA);
- // return new Set([slotA]); // A is successor of B -> fast-forward
- }
- return add(add(emptySet(compareSlots(compareElems)))(slotA))(slotB);
- // return new Set([slotA, slotB]);
-};
-
-export const merge2 = compareElems => mA => mB => {
- let result = emptySet(compareSlots(compareElems));
- forEach(mA)(slotOfA => {
- forEach(mB)(slotOfB => {
- const merged = merge(compareElems)(slotOfA)(slotOfB);
- forEach(merged)(merged => {
- result = add(result)(merged);
- });
- });
- });
- return result;
-};
diff --git a/lib/versioning/value.js b/lib/versioning/value.js
deleted file mode 100644
index 29304a5..0000000
--- a/lib/versioning/value.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import { inspect } from "node:util";
-
-// a -> Value
-export const newLiteral = val => ({
- kind: "literal",
- out: val,
- [inspect.custom]: (depth, options, inspect) => `newLiteral(${inspect(val)})`,
-});
-
-// Slot -> Value
-export const read = slot => ({
- kind: "read",
- slot,
- out: slot.value.out,
- [inspect.custom]: (depth, options, inspect) => `read(${inspect(slot)})`,
-});
-
-// Value -> Value b> -> Value
-export const transform = input => fn => {
- const output = fn.out(input.out);
- // const _inspect = (depth, options, inspect) => `transform(${inspect(input)}, ${inspect(fn)})`;
- if (input.kind === "literal") {
- // optimization: sandwich everything together
- return {
- kind: "literal",
- out: output,
- // [inspect.custom]: _inspect,
- };
- }
- else {
- return {
- kind: "transform",
- in: input,
- fn,
- out: output,
- // [inspect.custom]: _inspect,
- };
- }
-};
-
-// Value -> Set>
-export const getReadDependencies = value => {
- if (value.kind === "literal") {
- return new Set();
- }
- else if (value.kind === "read") {
- return new Set([value.slot]);
- }
- else if (value.kind === "transform") {
- return new Set([
- ...getReadDependencies(value.in),
- ...getReadDependencies(value.fn),
- ]);
- }
-};
-
-// for debugging
-export const verifyValue = (value, indent = 0) => {
- let success = true;
- const printIndent = (...args) => {
- console.log(" ".repeat(indent * 2), ...args);
- };
- const compare = (a, b, kind) => {
- if (typeof a === 'function' && typeof b === 'function') {
- printIndent("note: cannot compare functions", `(${kind})`);
- }
- else if (deepEqual(a, b)) {
- printIndent(`ok (${kind})`);
- }
- else {
- printIndent(`bad (${kind})`);
- success = false;
- }
- };
- if (value.kind === "literal") {
- compare(1, 1, "literal");
- }
- else if (value.kind === "read") {
- compare(value.out, value.slot.value.out, "read");
- }
- else if (value.kind === "transform") {
- compare(value.fn.out(value.in.out),
- value.out, "transform");
-
- success &= verifyValue(value.in, indent + 1);
- success &= verifyValue(value.fn, indent + 1);
- }
- return success;
-};
diff --git a/lib/versioning/types.js b/lib/versioning/value_slot.js
similarity index 60%
rename from lib/versioning/types.js
rename to lib/versioning/value_slot.js
index 7875b2d..3784ae8 100644
--- a/lib/versioning/types.js
+++ b/lib/versioning/value_slot.js
@@ -1,15 +1,9 @@
-import { compareDynamic } from "../compare/dynamic.js";
-import { compareInts, compareSymbols } from "../compare/primitives.js";
import { makeTypeConstructor } from "../meta/type_constructor.js";
import { makeTypeParser } from "../parser/type_parser.js";
import { apply, newDynamic } from "../primitives/dynamic.js";
import { Dynamic, Int, UUID } from "../primitives/primitive_types.js";
-import { makeEnumCompareFn } from "../structures/enum.js";
import { makeModuleEnum } from "../structures/enum.types.js";
-import { add, emptySet } from "../structures/set.js";
-import { makeStructCompareFn } from "../structures/struct.js";
import { makeModuleStruct } from "../structures/struct.types.js";
-import { pretty } from "../util/pretty.js";
export const Slot = makeTypeConstructor("Slot__318d1c1a9336c141336c461c6a3207b0")(0);
export const Value = makeTypeConstructor("Value__23fc00a2db1374bd3dc1a0ad2d8517ab")(0);
@@ -108,60 +102,6 @@ export const newSlot = newSlotNew;
export const toSlot = newSlotWrite;
export const newLiteral = newValueLiteral;
-// comparison functions are mutually recursive...
-
-const compareSlot = makeEnumCompareFn([
- {l: "new" , r: () => compareSymbols},
- {l: "write", r: () => compareWrite},
-]);
-
-const compareWrite = makeStructCompareFn([
- {l: "slot" , r: () => compareSlot},
- {l: "value", r: () => compareValue},
- {l: "depth", r: () => compareInts},
-]);
-
-const compareTransformation = makeStructCompareFn([
- {l: "input" , r: () => compareValue},
- {l: "fn" , r: () => compareValue},
- {l: "output", r: () => compareDynamic},
-]);
-
-const compareValue = makeEnumCompareFn([
- {l: "literal" , r: () => compareDynamic},
- {l: "read" , r: () => compareWrite},
- {l: "transform", r: () => compareTransformation},
-]);
-
-export const findLCA = slotA => slotB => {
- if (compareSlot(slotA)(slotB) === 0) {
- return slotA;
- }
- if (getDepth(slotA) > getDepth(slotB)) {
- // we can assume that slotA.variant === "write"
- return findLCA(slotA.value.slot)(slotB);
- }
- else {
- // we can assume that slotB.variant === "write"
- return findLCA(slotA)(slotB.value.slot);
- }
-}
-
-const emptySetOfSlots = emptySet(compareSlot);
-
-export const merge = slotA => slotB => {
- const lca = findLCA(slotA)(slotB);
- if (compareSlot(lca)(slotA) === 0) {
- return add(emptySetOfSlots)(slotB);
- }
- if (compareSlot(lca)(slotB) === 0) {
- return add(emptySetOfSlots)(slotA);
- }
- const setWithA = add(emptySetOfSlots)(slotA);
- const setWithAandB = add(setWithA)(slotB);
- return setWithAandB;
-}
-
const mkType = makeTypeParser({
extraPrimitives: [
["Value", Value],
@@ -174,20 +114,12 @@ export const ModuleVersioning = [
// slots
["newSlot" , newDynamic(newSlot )(mkType("UUID -> Slot" ))],
["write" , newDynamic(write )(mkType("Slot -> Value -> Slot" ))],
-
- // utilty
["getDepth" , newDynamic(getDepth )(mkType("Slot -> Int" ))],
// values
["newLiteral", newDynamic(newLiteral)(mkType("Dynamic -> Value" ))],
["read" , newDynamic(read )(mkType("Slot -> Value" ))],
["transform" , newDynamic(transform )(mkType("Value -> Value -> Value"))],
-
- // comparison
- ["compareSlot" , newDynamic(compareSlot )(mkType("Slot -> Slot -> Ordering"))],
- ["compareValue", newDynamic(compareValue)(mkType("Value -> Value -> Ordering"))],
- // ["compareWrite", newDynamic(compareWrite)(mkType("Write -> Write -> Ordering"))],
- // ["compareTransformation", newDynamic(compareTransformation)(mkType("Transformation -> Transformation -> Ordering"))],
];
diff --git a/snippets/versioning.js b/snippets/versioning.js
index 8f3bf20..e95ba55 100644
--- a/snippets/versioning.js
+++ b/snippets/versioning.js
@@ -1,8 +1,10 @@
import { newDynamic } from "../lib/primitives/dynamic.js";
-import { Int } from "../lib/primitives/primitive_types.js";
+import { Char, Int } from "../lib/primitives/primitive_types.js";
import { fnType } from "../lib/structures/type_constructors.types.js";
import { pretty } from "../lib/util/pretty.js";
-import { merge, newLiteral, newSlot, read, transform, write } from "../lib/versioning/types.js";
+import { newLiteral, newSlot, read, transform, write } from "../lib/versioning/value_slot.js";
+import { merge, mergeN } from "../lib/versioning/merge.js";
+import { union } from "../lib/structures/set.js";
const inc = x => x + 1n;
const incLiteral = newLiteral(newDynamic(inc)(fnType(_=>Int)(_=>Int)));
@@ -64,20 +66,23 @@ console.log("###############");
console.log("## Branching ##");
console.log("###############");
-const fiveSlot = write
- (newSlot("Counter__4a029b3d758bcd1fffbf495531c95537"))
- (newLiteral(newDynamic(5n)(Int)));
-const sixSlot = write(fiveSlot)(newLiteral(newDynamic(6n)(Int)));
-const sevenSlot = write(fiveSlot)(newLiteral(newDynamic(7n)(Int)));
-const eightSlot = write(fiveSlot)(newLiteral(newDynamic(8n)(Int)));
+const initialSlot = newSlot("Slot__4a029b3d758bcd1fffbf495531c95537");
-const sixSevenSlot = merge(sixSlot)(sevenSlot);
+const slotA = write(initialSlot)(newLiteral(newDynamic("a")(Char)));
+const slotB = write(slotA )(newLiteral(newDynamic("b")(Char)));
+const slotC = write(initialSlot)(newLiteral(newDynamic("c")(Char)));
-console.log(pretty(sixSevenSlot));
+const slotAB = merge(slotA)(slotB);
+const slotAC = merge(slotA)(slotC);
+const slotBC = merge(slotB)(slotC);
-// const sevenEightSlot = merge(sevenSlot)(eightSlot);
+console.log({slotAB, slotAC, slotBC});
-// const sixSevenEightSlot = merge(sixSevenSlot)(sevenEightSlot);
+const un = union(slotAC)(slotBC);
+
+const merged = mergeN(un);
+
+console.log("merged:", merged);
// // console.log(compareSlots(intCompare)(fiveSlot)(fiveSlot));
// // console.log(compareSlots(intCompare)(sixSlot)(sixSlot));