progress with versioning

This commit is contained in:
Joeri Exelmans 2025-06-06 16:39:47 +02:00
parent 618cdf7314
commit e106ced1ec
8 changed files with 151 additions and 238 deletions

50
lib/versioning/compare.js Normal file
View file

@ -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"))],
];

73
lib/versioning/merge.js Normal file
View file

@ -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}" ))],
];

View file

@ -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;
};

View file

@ -1,89 +0,0 @@
import { inspect } from "node:util";
// a -> Value<a>
export const newLiteral = val => ({
kind: "literal",
out: val,
[inspect.custom]: (depth, options, inspect) => `newLiteral(${inspect(val)})`,
});
// Slot<a> -> Value<a>
export const read = slot => ({
kind: "read",
slot,
out: slot.value.out,
[inspect.custom]: (depth, options, inspect) => `read(${inspect(slot)})`,
});
// Value<a> -> Value<a -> b> -> Value<b>
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<a> -> Set<Slot<Top>>
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;
};

View file

@ -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"))],
];