branching and very basic merging of slots

This commit is contained in:
Joeri Exelmans 2025-04-17 09:19:41 +02:00
parent 614e6c0fdb
commit 3978f7f835
32 changed files with 684 additions and 420 deletions

42
scripts/generics.js Normal file
View file

@ -0,0 +1,42 @@
import { Bool, Int } from "../primitives/types.js";
import { fnType, lsType, prettyT } from "../structures/types.js";
import { assign, makeGeneric, unify } from "../generics/generics.js";
// a -> Int
const a_to_Int = makeGeneric(a => fnType(a)(Int));
// Bool -> Int
const Bool_to_Int = makeGeneric(() => fnType(lsType(Bool))(Int));
console.log("should be: [Bool] -> Int")
console.log(prettyT(unify(a_to_Int, Bool_to_Int)));
// (a -> a) -> b
const fnType2 = makeGeneric((a,b) => fnType(fnType(a)(a))(b));
// (Bool -> Bool) -> a
const fnType3 = makeGeneric(a => fnType(fnType(Bool)(Bool))(a));
console.log("should be: (Bool -> Bool) -> a");
console.log(prettyT(unify(fnType2, fnType3)));
// (a -> b) -> [a] -> [b]
const mapFnType = makeGeneric((a,b) =>
fnType
(fnType(a)(b))
(fnType(lsType(a))(lsType(b))))
// a -> a
const idFnType = makeGeneric((_,__,c) =>
fnType(c)(c));
console.log("should be: [c] -> [c]");
console.log(prettyT(assign(mapFnType, idFnType)));
// (a -> Int) -> [a] -> a
const weirdFnType = makeGeneric(a =>
fnType
(fnType(a)(Int))
(fnType
(lsType(a))
(a)))
// we call this function with parameter of type (b -> b) ...
// giving these substitutions:
// a := b
// b := Int
console.log("should be: [Int] -> Int");
console.log(prettyT(assign(weirdFnType, idFnType)));

View file

@ -6,8 +6,8 @@ import { isFunction, prettyT } from '../structures/types.js';
import { ModuleStd } from '../stdlib.js';
import { Double, GenericType, Int, SymbolT, Type } from "../primitives/types.js";
import { eqType } from '../primitives/type.js';
import { Any } from '../typed.js';
import { assign, assignFn, makeGeneric, onlyOccurring } from '../generics/generics.js';
import { Any } from "../primitives/types.js";
import { assignFn, makeGeneric, onlyOccurring } from '../generics/generics.js';
// import {emitKeypressEvents} from 'node:readline';
@ -48,6 +48,15 @@ class Context {
this.types.getdefault(i, true).add(t);
this.types.getdefault(i, true).add(Any);
if (t.typeVars) {
// console.log("generic:", prettyT(t));
this.types.getdefault(t, true).add(GenericType);
}
else {
// console.log("non-generic:", prettyT(t));
this.types.getdefault(t, true).add(Type);
}
this.instances.getdefault(t, true).add(i);
this.instances.getdefault(Any, true).add(i);
}
@ -318,7 +327,7 @@ async function callFunction(fn, fnT) {
}
else {
inType = fnT.params[0];
choices = [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)]));
choices = [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, [inType]]));
}
const choice = await select({
@ -337,7 +346,7 @@ async function callFunction(fn, fnT) {
i = await createInstance(inType);
t = inType;
if (i === undefined) {
return;
return callFunction(fn, fnT);
}
}
else {
@ -345,7 +354,8 @@ async function callFunction(fn, fnT) {
t = choice.t;
}
const genT = t.typeVars ? t : makeGeneric(() => t);
const assignedFnType = assignFn(fnT, genT);
const genFnT = fnT.typeVars ? fnT : makeGeneric(() => fnT);
const assignedFnType = assignFn(genFnT, genT);
await apply(i, fn, assignedFnType);
return callFunction(fn, fnT);
}
@ -417,11 +427,10 @@ async function transform(i, t) {
async function apply(i, fn, fnT) {
const result = fn(i);
// console.log(fn, '(', i, ')', '=', result);
let resultType;
// console.log(fnT);
if (fnT.typeVars) {
resultType = onlyOccurring(fnT.type.params[1], fnT.typeVars);
const res = onlyOccurring(fnT.type.params[1], fnT.typeVars);
resultType = res.typeVars.size > 0 ? res : res.type;
}
else {
resultType = fnT.params[1];

28
scripts/num.js Normal file
View file

@ -0,0 +1,28 @@
import { assign } from "../generics/generics.js";
import { makeGeneric } from "../generics/generics.js";
import { Double, Int } from "../primitives/types.js";
import { fnType } from "../structures/types.js";
import { pretty } from '../util/pretty.js';
import { getMul, NumInstances } from "../typeclasses/num.js";
import { numDictType } from "./num_type.js";
const square = numDict => x => getMul(numDict)(x)(x);
// NumDict a -> a -> a
const squareFnType = makeGeneric(a =>
fnType
(numDictType(a))
(fnType(a)(a))
);
console.log("should be: Int -> Int");
console.log(pretty(assign(squareFnType, makeGeneric(() => numDictType(Int)))));
console.log("should be: Double -> Double");
console.log(pretty(assign(squareFnType, makeGeneric(() => numDictType(Double)))));
// to call 'square' we need:
// - the type of our argument (=Int)
// - access to a mapping from types to their typeclass instantiation
console.log("");
console.log(square(NumInstances.get(Int))(42n)); // 1764n

View file

@ -54,7 +54,7 @@ function benchmark(N) {
const endTime = Date.now();
return endTime - startTime;
}
const durCopying = (N <= 20000) ? copying() : "";
const durCopying = (N <= 50000) ? copying() : "";
console.log("copying:", durCopying, "ms");
// slower than slowest

122
scripts/versioning.js Normal file
View file

@ -0,0 +1,122 @@
import { pretty } from "../util/pretty.js";
import { newLiteral, transform, read, getReadDependencies, verifyValue } from "../versioning/value.js";
import { merge, merge2, newSlot, overwrite } from "../versioning/slot.js";
import createRBTree from "functional-red-black-tree";
import { add, emptySet, RBTreeWrapper } from "../structures/set.js";
const inc = x => x + 1;
console.log("##############");
console.log("## Counting ##");
console.log("##############");
const counterOneSlot = newSlot(Symbol('counter'))(newLiteral(1));
const valueOne = read(counterOneSlot);
console.log(pretty({valueOne}));
const onePlusOne = transform(valueOne)(newLiteral(inc));
console.log(pretty({onePlusOne}));
const onePlusOnePlusOne = transform(onePlusOne)(newLiteral(inc));
console.log(pretty({onePlusOnePlusOne}));
verifyValue(valueOne);
verifyValue(onePlusOne);
verifyValue(onePlusOnePlusOne);
console.log("#############");
console.log("## Summing ##");
console.log("#############");
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)));
console.log(pretty({total}))
const totalPlusOne = transform(total)(newLiteral(inc));
console.log(pretty({totalPlusOne}));
verifyValue(totalPlusOne);
console.log("getReadDependencies(totalPlusOne):", getReadDependencies(totalPlusOne));
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 numCompare = x => y => {
if (typeof(x) !== 'number' || typeof(y) !== 'number') {
throw new Error(`was only meant to compare numbers! got ${x} and ${y}`);
}
return (x < y) ? -1 : (x > y) ? 1 : 0;
};
const intMerge = merge(numCompare);
const intMerge2 = merge2(numCompare);
const sixSevenSlot = intMerge(sixSlot)(sevenSlot);
const sevenEightSlot = intMerge(sevenSlot)(eightSlot);
// 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 sixSevenEightSlot = intMerge2(sixSevenSlot)(sevenEightSlot);
console.log(pretty({sixSevenEightSlot}));
// console.log("########################");
// console.log("## Heterogeneous data ##");
// console.log("########################");
// const strCompare = x => y => {
// if (typeof(x) !== 'string' || typeof(y) !== 'string') {
// throw new Error(`was only meant to compare strings! got ${x} and ${y}`);
// }
// return (x < y) ? -1 : (x > y) ? 1 : 0;
// };
// // Slot<Int>
// const numberOfSheepSlot = newSlot(Symbol('numberOfSheep'))(newLiteral(5));
// const alternativeNumberOfSheepSlot = newSlot(Symbol('alternativeNumberOfSheep'))(newLiteral(6));
// // Slot<String>
// const labelSlot = newSlot(Symbol('label'))(newLiteral("number of sheep"));
// const combineFn = newLiteral(label => numberOfSheep => `${label}: ${numberOfSheep}`)
// // Slot<String>
// 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(strCompare)))(labelAndValueSlotA))(labelAndValueSlotB)
// );
// merge()(labelSlot)(labelAndValueSlot)
console.log("#############")
console.log("## RB Tree ##")
console.log("#############")
// just a small experiment
console.log(new RBTreeWrapper(createRBTree().insert(1).insert(1).insert(2)));

View file

@ -1,130 +0,0 @@
import { pretty } from "../util/pretty.js";
import { deepEqual } from "../util/util.js";
// UUID -> Computation<a> -> Slot<a>
const newSlot = uuid => computation => ({
overwrites: uuid,
computation,
// depth: 1,
});
// a -> Computation<a>
const newValue = val => ({
kind: "value",
out: val,
});
// Slot<a> -> Computation<a>
const read = slot => ({
kind: "read",
slot,
out: slot.computation.out,
});
// Computation<a> -> Computation<a -> b> -> Computation<b>
const transform = input => fn => {
const output = fn.out(input.out);
if (input.kind === "value") {
// optimization: sandwich everything together
return {
kind: "value",
out: output,
};
}
else {
return {
kind: "transformation",
in: input,
fn,
out: output,
};
}
};
const verifyComputation = (computation, indent=0) => {
const printIndent = (...args) => {
console.log(" ".repeat(indent*2), ...args);
}
const compare = (a,b, kind) => {
if (typeof a === 'function' && typeof b === 'function') {
printIndent("cannot compare functions", `(${kind})`);
}
else if (deepEqual(a, b)) {
printIndent(`ok (${kind})`);
}
else {
printIndent(`bad (${kind})`);
}
}
if (computation.kind === "value") {
compare(1, 1, "value");
}
else if (computation.kind === "read") {
compare(computation.out, computation.slot.computation.out, "read");
}
else if (computation.kind === "transformation") {
compare(computation.fn.out(computation.in.out),
computation.out, "transformation");
verifyComputation(computation.in, indent+1);
verifyComputation(computation.fn, indent+1);
}
}
const getReadDependencies = computation => {
if (computation.kind === "value") {
return new Set();
}
else if (computation.kind === "read") {
return new Set([computation.slot]);
}
else if (computation.kind === "transformation") {
return new Set([
...getReadDependencies(computation.in),
...getReadDependencies(computation.fn),
]);
}
}
const inc = x => x + 1;
// const counterOne = newSlot(Symbol('counter'))(newValue(1));
// const valueOne = read(counterOne);
// console.log(pretty({valueOne}));
// const onePlusOne = transform(valueOne)(newValue(inc));
// console.log(pretty({onePlusOne}));
// const onePlusOnePlusOne = transform(onePlusOne)(newValue(inc));
// console.log(pretty({onePlusOnePlusOne}));
// verifyComputation(valueOne);
// verifyComputation(onePlusOne);
// verifyComputation(onePlusOnePlusOne);
const priceSlot = newSlot(Symbol('price'))(newValue(20.66));
const taxSlot = newSlot(Symbol('tax'))(newValue(4.34));
console.log(pretty({price: priceSlot}));
const computeTotal = tax => {
const addTax = price => price + tax;
return addTax;
};
const total =
transform(read(priceSlot))(
transform(read(taxSlot))(
newValue(computeTotal)));
console.log(pretty({total}))
const totalPlusOne = transform(total)(newValue(inc));
console.log(pretty({totalPlusOne}));
verifyComputation(totalPlusOne);
console.log("getReadDependencies(totalPlusOne):", getReadDependencies(totalPlusOne));