From d30f1312b6449900eea9110a2ff8819c1bd1e487 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Sun, 8 Jun 2025 13:14:29 +0200 Subject: [PATCH 1/2] progress with versioning --- lib/versioning/merge.js | 22 +++++++++------------- lib/versioning/value_slot.js | 12 ++++++------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/versioning/merge.js b/lib/versioning/merge.js index 7ef70ba..ae80a03 100644 --- a/lib/versioning/merge.js +++ b/lib/versioning/merge.js @@ -1,5 +1,5 @@ -import { emptySet, add, length, forEach, union, has } from "../structures/set.js"; -import { getDepth, Slot, Value, Write } from "./value_slot.js"; +import { emptySet, add, length, forEach, has, remove } from "../structures/set.js"; +import { getDepth, Slot } from "./value_slot.js"; import { compareSlot } from "./compare.js"; import { makeTypeParser } from "../parser/type_parser.js"; import { newDynamic } from "../primitives/dynamic.js"; @@ -20,6 +20,10 @@ export const findLCA = slotA => slotB => { } }; +// returns +// {slotA} if slotA is younger +// {slotB} if slotB is younger +// {slotA, slotB} if they are concurrent (conflicting) export const merge = slotA => slotB => { const lca = findLCA(slotA)(slotB); if (compareSlot(lca)(slotA) === 0) { @@ -34,7 +38,7 @@ export const merge = slotA => slotB => { }; export const mergeN = slots => { - let toDelete = emptySetOfSlots; + let result = slots; forEach(slots)(slotA => { forEach(slots)(slotB => { // compare all non-identical pairs @@ -44,26 +48,18 @@ export const mergeN = slots => { // if in the pair, one is an ancestor of the other, // only keep the other if (has(m)(slotA)) { - toDelete = add(toDelete)(slotB); + result = remove(result)(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], + ["Slot" , Slot], ], }); diff --git a/lib/versioning/value_slot.js b/lib/versioning/value_slot.js index 3784ae8..dbbc64d 100644 --- a/lib/versioning/value_slot.js +++ b/lib/versioning/value_slot.js @@ -20,8 +20,8 @@ const [ [, {i: matchValue}], ] = makeModuleEnum(Value)([ {l: "literal" , r: Dynamic}, - {l: "read" , r: Write}, // a 'read' reads the result of a Write - {l: "transform", r: Transformation}, + {l: "read" , r: Write}, // <- a 'read' reads the result of a Write (to a Slot) + {l: "transform", r: Transformation}, // <- result of a function call ]); // Enum: Slot @@ -36,7 +36,7 @@ const [ {l: "write", r: Write}, ]); -// Struct: Transformation +// Struct: Transformation (i.e., a function call) const [ // constructor [, {i: newTransformation}], @@ -45,9 +45,9 @@ const [ // [, {i: getFn}], // [, {i: getOutput}], ] = makeModuleStruct(Transformation)([ - {l: "input" , r: Value }, - {l: "fn" , r: Value }, - {l: "output", r: Dynamic}, + {l: "input" , r: Value }, // <- the input is a value (e.g., could be the result of a Transformation itself) + {l: "fn" , r: Value }, // <- the function is also a value (e.g., could also be result of a transformation, e.g., when currying) + {l: "output", r: Dynamic}, // <- the result ]); // Struct: Write From 176bb5c109d531f1294b2297ae70f07e7e85ddf8 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Sun, 8 Jun 2025 14:09:41 +0200 Subject: [PATCH 2/2] create statically typed variant of versioning lib --- lib/versioning/static/compare.js | 55 ++++++++++++ lib/versioning/static/merge.js | 69 ++++++++++++++ lib/versioning/static/value_slot.js | 134 ++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 lib/versioning/static/compare.js create mode 100644 lib/versioning/static/merge.js create mode 100644 lib/versioning/static/value_slot.js diff --git a/lib/versioning/static/compare.js b/lib/versioning/static/compare.js new file mode 100644 index 0000000..bfb8429 --- /dev/null +++ b/lib/versioning/static/compare.js @@ -0,0 +1,55 @@ +import { compareSymbols, compareInts } from "../../compare/primitives.js"; +import { compareFunctions } from "../../compare/structures.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 = compareElems => makeEnumCompareFn([ + { l: "new", r: () => compareSymbols }, + { l: "write", r: () => compareWrite(compareElems) }, +]); + +export const compareWrite = compareElems => makeStructCompareFn([ + { l: "slot", r: () => compareSlot(compareElems) }, + { l: "value", r: () => compareValue(compareElems) }, + { l: "depth", r: () => compareInts }, +]); + +export const compareTransformation = compareInputs => compareOutputs => makeStructCompareFn([ + { l: "input", r: () => compareValue(compareInputs) }, + { l: "fn", r: () => compareValue(compareFunctions(compareInputs)(compareOutputs)) }, + { l: "output", r: () => compareOutputs }, +]); + +export const compareValue = compareElems => makeEnumCompareFn([ + { l: "literal", r: () => compareElems }, + { l: "read", r: () => compareWrite(compareElems) }, + { l: "transform", r: () => + compareTransformation + (_a => _b => 0) // <- we cheat here, because we don't statically know the input type of the transformation. + (compareElems) }, +]); + +const mkType = makeTypeParser({ + extraBracketOperators: [ + [':=', ['=:', Slot]], + ['*=', ['=*', Value]], + ['*:=', ['=:*', Write]], + ], + extraInfixOperators: [ + ['===>', Transformation], + ], +}); + +export const ModuleVersioningCompare = [ + // comparison + ["compareSlot" , newDynamic(compareSlot )(mkType("(a -> a -> Ordering) -> :=a=: -> :=a=: -> Ordering"))], + ["compareValue", newDynamic(compareValue)(mkType("(a -> a -> Ordering) -> *=a=* -> *=a=* -> Ordering"))], + ["compareWrite", newDynamic(compareWrite)(mkType("(a -> a -> Ordering) -> *:=a=:* -> *:=a=:* -> Ordering"))], + ["compareTransformation", newDynamic(compareTransformation)(mkType("(a -> a -> Ordering) -> (b -> b -> Ordering) -> (a ===> b) -> (a ===> b) -> Ordering"))], +]; diff --git a/lib/versioning/static/merge.js b/lib/versioning/static/merge.js new file mode 100644 index 0000000..1243f7b --- /dev/null +++ b/lib/versioning/static/merge.js @@ -0,0 +1,69 @@ +import { emptySet, add, length, forEach, has, remove } from "../../structures/set.js"; +import { getDepth, Slot } 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); + } +}; + +// returns +// {slotA} if slotA is younger +// {slotB} if slotB is younger +// {slotA, slotB} if they are concurrent (conflicting) +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 result = slots; + 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)) { + result = remove(result)(slotB); + } + } + } + }); + }); + return result; +}; + +const mkType = makeTypeParser({ + extraBracketOperators: [ + [':=', ['=:', Slot]], + ], +}); + +export const ModuleStaticMerge = [ + ["merge" , newDynamic(merge)(mkType(":=a=: -> :=a=: -> {:=a=:}"))], + ["mergeN", newDynamic(merge)(mkType("{:=a=:} -> {:=a=:}" ))], +]; diff --git a/lib/versioning/static/value_slot.js b/lib/versioning/static/value_slot.js new file mode 100644 index 0000000..7e680ab --- /dev/null +++ b/lib/versioning/static/value_slot.js @@ -0,0 +1,134 @@ +import { makeGeneric } from "../../generics/generics.js"; +import { makeTypeConstructor } from "../../meta/type_constructor.js"; +import { makeTypeParser } from "../../parser/type_parser.js"; +import { apply, newDynamic } from "../../primitives/dynamic.js"; +import { Int, UUID } from "../../primitives/primitive_types.js"; +import { makeModuleEnum } from "../../structures/enum.types.js"; +import { makeModuleStruct } from "../../structures/struct.types.js"; +import { fnType } from "../../structures/type_constructors.types.js"; + +// Value and Slot each take 1 type parameter +export const Slot = makeTypeConstructor("Slot__318d1c1a9336c141336c461c6a3207b0")(1); +export const Value = makeTypeConstructor("Value__23fc00a2db1374bd3dc1a0ad2d8517ab")(1); + +// Transformation takes 2 type parameters +export const Transformation = makeTypeConstructor("Transformation__9eac70d7020c7c45d5a16f3f14b13083")(2); + +// A Write-operation writes a value to a slot. The inner type of the Value and Slot must match. +export const Write = makeTypeConstructor("Write__abaef8ddb5c167b5d2cedac111fcefd3")(1); + +// Enum: Value +const [ + // constructors + [, {i: newValueLiteral}], + [, {i: newValueRead}], + [, {i: newValueTransform}], + // match function + [, {i: matchValue}], +] = makeGeneric((innerType, a) => makeModuleEnum(Value(_=>innerType))([ + {l: "literal" , r: innerType}, + {l: "read" , r: Write(_=>innerType)}, // <- a 'read' reads the result of a Write (to a Slot) + {l: "transform", r: Transformation(_=>a)(_=>innerType)}, // <- result of a function call +])); + +// Enum: Slot +const [ + // constructors + [, {i: newSlotNew}], + [, {i: newSlotWrite}], + // match function + [, {i: matchSlot}], +] = makeGeneric(innerType => makeModuleEnum(Slot(_=>innerType))([ + {l: "new" , r: UUID }, + {l: "write", r: Write}, +])); + +// Struct: Transformation (i.e., a function call) +const [ + // constructor + [, {i: newTransformation}], + // getters + // [, {i: getInput}], + // [, {i: getFn}], + // [, {i: getOutput}], +] = makeGeneric((inType, outType) => makeModuleStruct(Transformation)([ + {l: "input" , r: Value(_=>inType) }, // <- the input is a value (e.g., could be the result of a Transformation itself) + {l: "fn" , r: Value(_=>fnType(_=>inType)(_=>outType)) }, // <- the function is also a value (e.g., could also be result of a transformation, e.g., when currying) + {l: "output", r: outType}, // <- the result +])); + +// Struct: Write +const [ + // constructor + [, {i: newWrite}], + // getters + // [, {i: getDepth}], + // [, {i: getSlot}], + // [, {i: getValue}], +] = makeGeneric(innerType => makeModuleStruct(Write(_=>innerType))([ + {l: "depth", r: Int }, // <- depth increases merge performance, and also comes first in the struct (this way, it is the first value compared upon comparison) + {l: "slot" , r: Slot(_=>innerType) }, + {l: "value", r: Value(_=>innerType) }, +])); + +// shorthands +export const getDepth = slot => + matchSlot(slot) + (_slotNew => 0n) + (slotWrite => slotWrite.depth); + +// get the value from a Value +export const getVal = value => + matchValue(value) + (literal => literal) + (read => read.value.value) + (transform => transform.output) + +export const write = slot => value => + newSlotWrite( + newWrite + (getDepth(slot)+1n) + (slot) + (value) + ); + +export const transform = inValue => fnValue => + newValueTransform + (newTransformation + (inValue) + (fnValue) + (apply(getVal(fnValue))(getVal(inValue))) // <- call function + ); + +export const read = slot => + matchSlot(slot) + (_slotNew => { throw new Error("cannot read empty slot"); }) + (write => write.value); + +export const newSlot = newSlotNew; +export const toSlot = newSlotWrite; +export const newLiteral = newValueLiteral; + +const mkType = makeTypeParser({ + extraBracketOperators: [ + [':=', ['=:', Slot]], + ['*=', ['=*', Value]], + ], + extraInfixOperators: [ + ['===>', Transformation], + ], +}); + +export const ModuleStaticVersioning = [ + // slots + ["newSlot" , newDynamic(newSlot )(mkType("UUID -> :=a=:" ))], + ["write" , newDynamic(write )(mkType(":=a=: -> *=a=* -> :=a=:"))], + ["getDepth" , newDynamic(getDepth )(mkType(":=a=: -> Int" ))], + + // values + ["newLiteral", newDynamic(newLiteral)(mkType("a -> *=a=*" ))], + ["read" , newDynamic(read )(mkType(":=a=: -> *=a=*" ))], + ["transform" , newDynamic(transform )(mkType("*=a=* -> *=(a->b)=* -> *=b=*"))], +]; + +