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));