From 618cdf73149070e01982fb51df85a45f2c5f0ede Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 6 Jun 2025 15:30:35 +0200 Subject: [PATCH] progress with versioning lib --- lib/compare/dynamic.js | 1 - lib/generics/unify.js | 14 +++- lib/primitives/dynamic.js | 19 +++--- lib/structures/enum.js | 12 ++++ lib/structures/struct.js | 10 +++ lib/versioning/types.js | 138 ++++++++++++++++++++++++++++++++------ snippets/versioning.js | 133 ++++++++++++++++++++---------------- 7 files changed, 236 insertions(+), 91 deletions(-) diff --git a/lib/compare/dynamic.js b/lib/compare/dynamic.js index 98a115f..9454705 100644 --- a/lib/compare/dynamic.js +++ b/lib/compare/dynamic.js @@ -27,7 +27,6 @@ const typeSymbolToCmp = new Map([ [symbolUnit , compareUnits ], [symbolBottom , _ => _ => { throw new Error("Bottom!"); }], [symbolUUID , compareSymbols ], - // [SymbolGenericType, ?] TODO [symbolType , compareTypes ], [symbolDynamic , compareDynamic ], [symbolOrdering, compareOrderings], diff --git a/lib/generics/unify.js b/lib/generics/unify.js index dde60fc..3455208 100644 --- a/lib/generics/unify.js +++ b/lib/generics/unify.js @@ -1,10 +1,10 @@ import { eqType } from "../primitives/type.js"; import { isTypeVar } from "../primitives/typevars.js"; -import { prettyT } from "../util/pretty.js"; +import { pretty, prettyT } from "../util/pretty.js"; import { indent, zip } from "../util/util.js"; import { compareStrings } from "../compare/primitives.js"; import { getHumanReadableName } from "../primitives/symbol.js"; -import { occurring, substitute } from "./generics.js"; +import { occurring, recomputeTypeVars, substitute } from "./generics.js"; export const prettyS = (typevar, type) => { return `${getHumanReadableName(typevar)} ↦ ${prettyT(type)}`; @@ -180,3 +180,13 @@ export const unify = (typeA, typeB) => { throw e; } }; + +export const assignFn = (functionType, parameterType) => { + const [rFunctionType, rParameterType] = recomputeTypeVars([functionType, parameterType]); + console.log(pretty(rFunctionType)); + const rFunctionInType = rFunctionType.params[0](rFunctionType); + const rFunctionOutType = rFunctionType.params[1](rFunctionType); + const subs = unify(rFunctionInType, rParameterType); + const result = substitute(rFunctionOutType, subs); + return recomputeTypeVars([result])[0]; +} \ No newline at end of file diff --git a/lib/primitives/dynamic.js b/lib/primitives/dynamic.js index 5fae380..dff658b 100644 --- a/lib/primitives/dynamic.js +++ b/lib/primitives/dynamic.js @@ -1,4 +1,5 @@ import { inspect } from "node:util"; +import { assignFn } from "../generics/unify.js"; // import { assignFn } from "../generics/generics.js"; function inspectDynamic(_depth, options, inspect) { @@ -13,14 +14,14 @@ export const newDynamic = i => t => ({ export const getInst = lnk => lnk.i; export const getType = lnk => lnk.t; -// export const apply = input => fun => { -// const inputType = getType(input) -// const funType = getType(fun); -// const outputType = assignFn(funType, inputType); +export const apply = fun => input => { + const inputType = getType(input) + const funType = getType(fun); + const outputType = assignFn(funType, inputType); -// const inputValue = getInst(input); -// const funValue = getInst(fun); -// const outputValue = funValue(inputValue); + const inputValue = getInst(input); + const funValue = getInst(fun); + const outputValue = funValue(inputValue); -// return newDynamic(outputValue)(outputType); -// }; + return newDynamic(outputValue)(outputType); +}; diff --git a/lib/structures/enum.js b/lib/structures/enum.js index 99b59fb..d2276f1 100644 --- a/lib/structures/enum.js +++ b/lib/structures/enum.js @@ -1,3 +1,4 @@ +import { compareStrings } from "../compare/primitives.js"; import { capitalizeFirstLetter } from "../util/util.js"; const eatParameters = (numParams, result) => { @@ -42,3 +43,14 @@ export const makeConstructors = variantNames => { return ctor; }) }; + +export const makeEnumCompareFn = variantCompares => { + return enumA => enumB => { + const cmp = compareStrings(enumA.variant)(enumB.variant); + if (cmp !== 0) { + return cmp; + } + const getCompareFn = variantCompares.find(({l: variantName}) => variantName === enumA.variant).r; + return getCompareFn()(enumA.value)(enumB.value); + } +}; diff --git a/lib/structures/struct.js b/lib/structures/struct.js index 35fa11d..943f384 100644 --- a/lib/structures/struct.js +++ b/lib/structures/struct.js @@ -28,3 +28,13 @@ export const makeGetters = fieldNames => { }[getterName]; }) }; + +export const makeStructCompareFn = compares => { + return a => b => { + for (const {l: fieldName, r: getCompareFn} of compares) { + const cmp = getCompareFn()(a[fieldName])(b[fieldName]); + if (cmp !== 0) return cmp; + } + return 0; + } +} \ No newline at end of file diff --git a/lib/versioning/types.js b/lib/versioning/types.js index f8b1f82..7875b2d 100644 --- a/lib/versioning/types.js +++ b/lib/versioning/types.js @@ -1,9 +1,15 @@ +import { compareDynamic } from "../compare/dynamic.js"; +import { compareInts, compareSymbols } from "../compare/primitives.js"; import { makeTypeConstructor } from "../meta/type_constructor.js"; -import { getDefaultTypeParser, makeTypeParser } from "../parser/type_parser.js"; -import { newDynamic } from "../primitives/dynamic.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); @@ -20,7 +26,7 @@ const [ [, {i: matchValue}], ] = makeModuleEnum(Value)([ {l: "literal" , r: Dynamic}, - {l: "read" , r: Slot}, + {l: "read" , r: Write}, // a 'read' reads the result of a Write {l: "transform", r: Transformation}, ]); @@ -32,7 +38,7 @@ const [ // match function [, {i: matchSlot}], ] = makeModuleEnum(Slot)([ - {l: "new" , r: UUID}, + {l: "new" , r: UUID }, {l: "write", r: Write}, ]); @@ -45,9 +51,9 @@ const [ // [, {i: getFn}], // [, {i: getOutput}], ] = makeModuleStruct(Transformation)([ - {l: "input" , r: Value}, - {l: "fn" , r: Value}, - {l: "output", r: Value}, + {l: "input" , r: Value }, + {l: "fn" , r: Value }, + {l: "output", r: Dynamic}, ]); // Struct: Write @@ -55,41 +61,133 @@ const [ // constructor [, {i: newWrite}], // getters + // [, {i: getDepth}], // [, {i: getSlot}], // [, {i: getValue}], - // [, {i: getDepth}], ] = makeModuleStruct(Write)([ - {l: "slot", r: Slot}, + {l: "depth", r: Int }, // <- depth comes first, for performance + {l: "slot" , r: Slot }, {l: "value", r: Value}, - {l: "depth", r: Int}, ]); // shorthands export const getDepth = slot => matchSlot(slot) - (_slotNew => 0) + (_slotNew => 0n) (slotWrite => slotWrite.depth); -export const overwrite = slot => value => + +// 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(slot)(value)(getDepth(slot)+1) + newWrite + (getDepth(slot)+1n) + (slot) + (value) ); -export const read = newValueRead; -export const transform = inValue => fnValue => newValueTransform(newTransformation(inValue)(fnValue)); + +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; +// 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], - ["Slot", Slot], - ] + ["Slot" , Slot ], + ["Write", Write], + ], }); export const ModuleVersioning = [ + // slots + ["newSlot" , newDynamic(newSlot )(mkType("UUID -> Slot" ))], + ["write" , newDynamic(write )(mkType("Slot -> Value -> Slot" ))], + + // utilty ["getDepth" , newDynamic(getDepth )(mkType("Slot -> Int" ))], - ["overwrite" , newDynamic(overwrite )(mkType("Slot -> Value -> Slot" ))], + + // values + ["newLiteral", newDynamic(newLiteral)(mkType("Dynamic -> Value" ))], ["read" , newDynamic(read )(mkType("Slot -> Value" ))], ["transform" , newDynamic(transform )(mkType("Value -> Value -> Value"))], - ["newSlot" , newDynamic(newSlot )(mkType("UUID -> Slot" ))], - ["newLiteral", newDynamic(newLiteral)(mkType("Dynamic -> 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 c9f862d..8f3bf20 100644 --- a/snippets/versioning.js +++ b/snippets/versioning.js @@ -1,50 +1,62 @@ +import { newDynamic } from "../lib/primitives/dynamic.js"; +import { Int } from "../lib/primitives/primitive_types.js"; +import { fnType } from "../lib/structures/type_constructors.types.js"; import { pretty } from "../lib/util/pretty.js"; -import { newLiteral, transform, read, getReadDependencies, verifyValue } from "../lib/versioning/value.js"; -import { merge, merge2, newSlot, overwrite } from "../lib/versioning/slot.js"; -import { compareNumbers } from "../lib/compare/primitives.js"; +import { merge, newLiteral, newSlot, read, transform, write } from "../lib/versioning/types.js"; -const inc = x => x + 1; +const inc = x => x + 1n; +const incLiteral = newLiteral(newDynamic(inc)(fnType(_=>Int)(_=>Int))); console.log("##############"); console.log("## Counting ##"); console.log("##############"); -const counterOneSlot = newSlot(Symbol('counter'))(newLiteral(1)); +const counterOneSlot = write + (newSlot("1d61577e704613f4e48b164852aedd20")) + (newLiteral(newDynamic(1n)(Int))); + +console.log(pretty({counterOneSlot})); + const valueOne = read(counterOneSlot); + console.log(pretty({valueOne})); -const onePlusOne = transform(valueOne)(newLiteral(inc)); + +const onePlusOne = transform + (valueOne) + (incLiteral); + console.log(pretty({onePlusOne})); -const onePlusOnePlusOne = transform(onePlusOne)(newLiteral(inc)); + +const onePlusOnePlusOne = transform + (onePlusOne) + (incLiteral); + console.log(pretty({onePlusOnePlusOne})); -verifyValue(valueOne); -verifyValue(onePlusOne); -verifyValue(onePlusOnePlusOne); +// console.log("#############"); +// console.log("## Summing ##"); +// console.log("#############"); -console.log("#############"); -console.log("## Summing ##"); -console.log("#############"); +// const priceSlot = newSlot(Symbol('price'))(newLiteral(20.66)); +// const taxSlot = newSlot(Symbol('tax'))(newLiteral(4.34)); -const priceSlot = newSlot(Symbol('price'))(newLiteral(20.66)); -const taxSlot = newSlot(Symbol('tax'))(newLiteral(4.34)); +// const total = +// transform(read(priceSlot))( +// transform(read(taxSlot))( +// newLiteral(tax => price => price + tax))); -const total = - transform(read(priceSlot))( - transform(read(taxSlot))( - newLiteral(tax => price => price + tax))); +// console.log(pretty({total})) -console.log(pretty({total})) +// const totalPlusOne = transform(total)(newLiteral(inc)); -const totalPlusOne = transform(total)(newLiteral(inc)); +// console.log(pretty({totalPlusOne})); -console.log(pretty({totalPlusOne})); +// verifyValue(totalPlusOne); -verifyValue(totalPlusOne); - -console.log("getReadDependencies(totalPlusOne):", getReadDependencies(totalPlusOne)); +// console.log("getReadDependencies(totalPlusOne):", getReadDependencies(totalPlusOne)); @@ -52,43 +64,46 @@ console.log("###############"); console.log("## Branching ##"); console.log("###############"); -const fiveSlot = newSlot(Symbol('counter'))(newLiteral(5)); -const sixSlot = overwrite(fiveSlot)(newLiteral(6)); -const sevenSlot = overwrite(fiveSlot)(newLiteral(7)); -const eightSlot = overwrite(fiveSlot)(newLiteral(8)); +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 intMerge = merge(compareNumbers); -const intMerge2 = merge2(compareNumbers); +const sixSevenSlot = merge(sixSlot)(sevenSlot); -const sixSevenSlot = intMerge(sixSlot)(sevenSlot); -const sevenEightSlot = intMerge(sevenSlot)(eightSlot); +console.log(pretty(sixSevenSlot)); -// console.log(compareSlots(intCompare)(fiveSlot)(fiveSlot)); -// console.log(compareSlots(intCompare)(sixSlot)(sixSlot)); -// console.log(compareSlots(intCompare)(fiveSlot)(sixSlot)); -// console.log(compareSlots(intCompare)(sixSlot)(fiveSlot)); +// const sevenEightSlot = merge(sevenSlot)(eightSlot); -const sixSevenEightSlot = intMerge2(sixSevenSlot)(sevenEightSlot); +// const sixSevenEightSlot = merge(sixSevenSlot)(sevenEightSlot); -console.log(pretty({sixSevenEightSlot})); +// // console.log(compareSlots(intCompare)(fiveSlot)(fiveSlot)); +// // console.log(compareSlots(intCompare)(sixSlot)(sixSlot)); +// // console.log(compareSlots(intCompare)(fiveSlot)(sixSlot)); +// // console.log(compareSlots(intCompare)(sixSlot)(fiveSlot)); -// console.log("########################"); -// console.log("## Heterogeneous data ##"); -// console.log("########################"); -// // Slot -// const numberOfSheepSlot = newSlot(Symbol('numberOfSheep'))(newLiteral(5)); -// const alternativeNumberOfSheepSlot = newSlot(Symbol('alternativeNumberOfSheep'))(newLiteral(6)); -// // Slot -// const labelSlot = newSlot(Symbol('label'))(newLiteral("number of sheep")); -// const combineFn = newLiteral(label => numberOfSheep => `${label}: ${numberOfSheep}`) -// // Slot -// const labelAndValueSlotA = overwrite(labelSlot)( -// transform(read(numberOfSheepSlot))( -// transform(read(labelSlot))(combineFn))); -// const labelAndValueSlotB = overwrite(labelSlot)( -// transform(read(alternativeNumberOfSheepSlot))( -// transform(read(labelSlot))(combineFn))); -// console.log( -// add(add(emptySet(compareSlots(compareStrings)))(labelAndValueSlotA))(labelAndValueSlotB) -// ); -// merge()(labelSlot)(labelAndValueSlot) + +// console.log(pretty({sixSevenEightSlot})); + +// // console.log("########################"); +// // console.log("## Heterogeneous data ##"); +// // console.log("########################"); +// // // Slot +// // const numberOfSheepSlot = newSlot(Symbol('numberOfSheep'))(newLiteral(5)); +// // const alternativeNumberOfSheepSlot = newSlot(Symbol('alternativeNumberOfSheep'))(newLiteral(6)); +// // // Slot +// // const labelSlot = newSlot(Symbol('label'))(newLiteral("number of sheep")); +// // const combineFn = newLiteral(label => numberOfSheep => `${label}: ${numberOfSheep}`) +// // // Slot +// // const labelAndValueSlotA = overwrite(labelSlot)( +// // transform(read(numberOfSheepSlot))( +// // transform(read(labelSlot))(combineFn))); +// // const labelAndValueSlotB = overwrite(labelSlot)( +// // transform(read(alternativeNumberOfSheepSlot))( +// // transform(read(labelSlot))(combineFn))); +// // console.log( +// // add(add(emptySet(compareSlots(compareStrings)))(labelAndValueSlotA))(labelAndValueSlotB) +// // ); +// // merge()(labelSlot)(labelAndValueSlot)