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

View file

@ -1,10 +1,11 @@
import { Type } from "../primitives/types.js"; import { Type } from "../primitives/types.js";
import { typedFnType } from "../structures/types.js" import { typedFnType } from "../structures/types.js"
import { Double } from "../primitives/types.js"; import { Double } from "../primitives/types.js";
import { makeTypeConstructor } from "../type_constructor.js";
// Create nominal types // Create nominal types
export const PointCartesian2D = { symbol: Symbol('PointCartesian2D'), params: [] }; export const PointCartesian2D = makeTypeConstructor(Symbol('PointCartesian2D'))(0);
export const PointPolar2D = { symbol: Symbol('PointPolar2D') , params: [] }; export const PointPolar2D = makeTypeConstructor(Symbol('PointPolar2D'))(0);
export const cart2polar = ({x, y}) => { export const cart2polar = ({x, y}) => {
const r = Math.sqrt(x*x + y*y); const r = Math.sqrt(x*x + y*y);

140
lib/point_structural.js Normal file
View file

@ -0,0 +1,140 @@
import { prettyT, typedFnType } from "../structures/types.js"
import { Double } from "../primitives/types.js";
import { makeConstructor, makeConstructorType, makeGetters, makeGettersTypes, structType } from "../structures/struct.js";
import { constructorProduct } from "../structures/product.js";
import { makeTypeConstructor } from "../type_constructor.js";
const cartFields = [
constructorProduct("x")(Double),
constructorProduct("y")(Double),
];
const polarFields = [
constructorProduct("r")(Double),
constructorProduct("θ")(Double),
];
// Nominal types:
export const NPoint2DCartesian = makeTypeConstructor(Symbol('Point2DCartesian'))(0);
export const NPoint2DPolar = makeTypeConstructor(Symbol('Point2DPolar'))(0);
// Structural types:
export const SPoint2DCartesian = structType(cartFields); // (Double, (Double, Unit))
export const SPoint2DPolar = structType(polarFields); // (Double, (Double, Unit))
export const constructorPoint2DCartesian = makeConstructor(cartFields);
export const constructorPoint2DPolar = makeConstructor(polarFields);
export const constructorPoint2DCartesianFnType = makeConstructorType(NPoint2DCartesian)(cartFields);
export const constructorPoint2DPolarFnType = makeConstructorType(NPoint2DPolar)(polarFields);
export const [getX, getY] = makeGetters(cartFields);
export const [getR, getΘ] = makeGetters(polarFields);
export const [getXFnType, getYFnType] = makeGettersTypes(NPoint2DCartesian)(cartFields);
export const [getRFnType, getΘFnType] = makeGettersTypes(NPoint2DPolar)(polarFields);
export const cart2polar = cart => {
const x = getX(cart);
const y = getY(cart);
const r = Math.sqrt(x*x + y*y);
const θ = Math.atan(y/x);
return constructorPoint2DPolar(r)(θ);
};
export const polar2cart = polar => {
const r = getR(polar);
const θ = getΘ(polar);
const x = r * Math.cos(θ);
const y = r * Math.sin(θ);
return constructorPoint2DCartesian(x)(y);
};
export const cart2Str = cart => {
const x = getX(cart);
const y = getY(cart);
return {l: `{x: ${x}, y: ${y}}`};
};
export const polar2Str = polar => {
const r = getR(polar);
const θ = getΘ(polar);
return {l: `{r: ${r}, θ: ${θ}}`};
};
// export const translate = dx => dy => ({x, y}) => {
// return {x: x+dx, y: y+dy};
// };
// export const rotate = dθ => ({r, θ}) => {
// return {r, θ: θ+dθ};
// };
// export const scale = dr => ({r, θ}) => {
// return {r: r+dr, θ};
// };
// const examplePoint = {x: 1, y: 2};
const examplePointCart = constructorPoint2DCartesian(1)(2);
const examplePointPolar = constructorPoint2DCartesian(0)(5);
console.log(prettyT(getXFnType));
export const ModulePoint = {l:[
// {i: -1 , t: Double},
// {i: 2 , t: Double},
// {i: examplePoint , t: Point2DCartesian},
{i: examplePointCart, t: SPoint2DCartesian},
{i: examplePointCart, t: NPoint2DCartesian},
{i: examplePointPolar, t: SPoint2DPolar},
{i: examplePointPolar, t: NPoint2DPolar},
// {i: SPoint2DCartesian , t: Type},
// {i: SPoint2DPolar , t: Type},
// {i: NPoint2DCartesian , t: Type},
// {i: NPoint2DPolar , t: Type},
{i: getX, t: getXFnType},
{i: getY, t: getYFnType},
{i: getR, t: getRFnType},
{i: getΘ, t: getΘFnType},
...typedFnType(cart2polar, fnType => fnType(NPoint2DCartesian)(NPoint2DPolar)),
...typedFnType(polar2cart, fnType => fnType(NPoint2DPolar)(NPoint2DCartesian)),
// ...typedFnType(polar2cart, fnType => fnType(Point2DPolar)(Point2DCartesian)),
// // Double -> Double -> PointCartesian2D -> PointCartesian2D
// ...typedFnType(translate, fnType =>
// fnType
// (Double)
// (fnType
// (Double)
// (fnType
// (Point2DCartesian)
// (Point2DCartesian)))),
// ...typedFnType(cart2tuple, fnType => fnType(Point2DCartesian)(prodType(Double)(Double))),
// ...typedFnType(polar2tuple, fnType => fnType(Point2DPolar)(prodType(Double)(Double))),
// // Double -> PointPolar2D -> PointPolar2D
// ...typedFnType(rotate, fnType =>
// fnType
// (Double)
// (fnType
// (Point2DPolar)
// (Point2DPolar))),
// // Double -> PointPolar2D -> PointPolar2D
// ...typedFnType(scale, fnType =>
// fnType
// (Double)
// (fnType
// (Point2DPolar)
// (Point2DPolar))),
]};

View file

@ -6,6 +6,8 @@ import { typedFnType } from "../structures/types.js";
// The way instances of SymbolT are currently encoded, their constructor is not a valid DOPE function, because it is impure. // The way instances of SymbolT are currently encoded, their constructor is not a valid DOPE function, because it is impure.
// The only way to construct symbols is to do it in JS code. // The only way to construct symbols is to do it in JS code.
// At some point, we should start encoding SymbolTs as UUIDs rather than JS-Symbols.
export const ModuleSymbol = {l:[ export const ModuleSymbol = {l:[
{i: SymbolT, t: Type}, {i: SymbolT, t: Type},

View file

@ -1,6 +1,6 @@
import { constructorLeft, constructorRight } from "../structures/sum.js"; import { constructorLeft, constructorRight } from "../structures/sum.js";
import { fnType, setType, sumType, typedFnType } from "../structures/types.js"; import { setType, sumType, typedFnType } from "../structures/types.js";
import { GenericType, SymbolT, Type, Unit } from "./types.js"; import { Any, GenericType, SymbolT, Type, Unit } from "./types.js";
import { unit } from "./unit.js"; import { unit } from "./unit.js";
export const getType = genericType => genericType.type; export const getType = genericType => genericType.type;
@ -11,11 +11,11 @@ export const toNonGeneric = genericType => (genericType.typeVars.size === 0)
: constructorLeft(unit); : constructorLeft(unit);
export const ModuleGenericType = {l:[ export const ModuleGenericType = {l:[
{i: GenericType, t: Type}, {i: GenericType, t: Any},
...typedFnType(getType, fnType => fnType(GenericType)(Type)), // ...typedFnType(getType, fnType => fnType(GenericType)(Type)),
...typedFnType(getTypeVars, fnType => fnType(GenericType)(setType(SymbolT))), // ...typedFnType(getTypeVars, fnType => fnType(GenericType)(setType(SymbolT))),
...typedFnType(toNonGeneric, fnType => fnType(GenericType)(sumType(Unit)(Type))), ...typedFnType(toNonGeneric, fnType => fnType(GenericType)(sumType(Unit)(Type))),
]}; ]};

View file

@ -10,6 +10,7 @@ const SymbolChar = Symbol('Char');
const SymbolUnit = Symbol('Unit'); const SymbolUnit = Symbol('Unit');
const SymbolSymbol = Symbol('Symbol'); const SymbolSymbol = Symbol('Symbol');
const SymbolType = Symbol('Type'); const SymbolType = Symbol('Type');
const symbolAny = Symbol('Any');
const SymbolGenericType = Symbol('GenericType'); const SymbolGenericType = Symbol('GenericType');
export const Int = makeTypeConstructor(SymbolInt)(0); export const Int = makeTypeConstructor(SymbolInt)(0);
@ -23,10 +24,13 @@ export const Unit = makeTypeConstructor(SymbolUnit)(0);
export const SymbolT = makeTypeConstructor(SymbolSymbol)(0); export const SymbolT = makeTypeConstructor(SymbolSymbol)(0);
// Types are typed by Any
export const Type = makeTypeConstructor(SymbolType)(0); export const Type = makeTypeConstructor(SymbolType)(0);
export const GenericType = makeTypeConstructor(SymbolGenericType)(0); export const GenericType = makeTypeConstructor(SymbolGenericType)(0);
// Everything is typed by Any
export const Any = makeTypeConstructor(symbolAny)(0);
export const ModuleSymbols = {l:[ export const ModuleSymbols = {l:[
{i: SymbolInt , t: SymbolT}, {i: SymbolInt , t: SymbolT},
@ -39,3 +43,5 @@ export const ModuleSymbols = {l:[
{i: SymbolType , t: SymbolT}, {i: SymbolType , t: SymbolT},
{i: SymbolGenericType, t: SymbolT}, {i: SymbolGenericType, t: SymbolT},
]}; ]};

View file

@ -1,72 +1,24 @@
status: done:
- everything is properly typed, up to the meta-circular level - everything is properly typed, up to the meta-circular level
- primitives - primitives
- structures: list, product, sum - structures: list, product, sum
can compose structures, e.g., create list of list of product of sum of ... can compose structures, e.g., create list of list of product of sum of ...
list type is specialized for ListOfByte to use Uint8Array list type is specialized for ListOfByte to use Uint8Array
- generics currently implemented in two ways: - generic types and type inferencing
1) similar to "templates" (as in C++):
a generic function or type is a function that takes a Type, and produces a specific variant
2) experimental implementation of polymorphic types and type inferencing
values currently treated as white-box, hardcoded generic types (e.g., list, function) in type inferencing algorithm
wip:
- interfaces via typeclasses?
- revise the way types are encoded
we need only one 'type of type' (called 'kind' in Haskell), and we can call it 'Type'.
types explicitly contain their underlying types. the type inferencing algorithm needs this information.
Int
{ symbol: Int, params: [] }
[Int]
{ symbol: List, params: [{ symbol: Int, params: [] }] }
[[Int]]
{ symbol: List,
params: [{ symbol: List, params: [{symbol: Int, params: []}]}]}
Int -> Bool
{ symbol: Function, params: [
{ symbol: Int, params: [] },
{ symbol: Bool, params: [] },
]
}
Int | Bool
{ symbol: Sum, params: [
{ symbol: Int, params: [] },
{ symbol: Bool, params: [] },
]
}
(Int, Bool)
{ symbol: Product, params: [
{ symbol: Int, params: [] },
{ symbol: Bool, params: [] },
]
}
Point2D <-- custom nominal type! maybe it contains two Doubles, but we don't expose this
{ symbol: Point2D, params: [] }
Type constructors are just functions that return a 'Type'.
For instance:
lsType :: Type -> Type
fnType :: Type -> Type -> Type
The sad(?) part about all of this, is that I'm converging with Haskell/Lean.
todo: todo:
- rename Type to a NominalType? - to support sets of slots, need comparison of slots
const nominalType = (name, params) => ({left: name, right: params}); => comparison of values
type of every nominal type is (String, [Type]) => problem: how to compare transformed values? their inputs can come from different types
(a) tedious: put in every value:
- the type
- a comparison function for that type
then first compare types, if types match, compare values.
could generalize this by writing a compare function on 'typed' values.
(b) dirty: use 'magic' function that compares any JS value
- typeclass mechanism
- what about type links: they connect anything to its type... what is the type of 'anything'? - what about type links: they connect anything to its type... what is the type of 'anything'?
- treat all values as polymorphic? (non-polymorphic values simply have empty set of type variables)
- type inferencing can be reduced to finding a graph isomorphism?

View file

@ -1,6 +1,6 @@
import { Bool, Int } from "../primitives/types.js"; import { Bool, Int } from "../primitives/types.js";
import { fnType, lsType, prettyT } from "../structures/types.js"; import { fnType, lsType, prettyT } from "../structures/types.js";
import { assign, makeGeneric, unify } from "./generics.js"; import { assign, makeGeneric, unify } from "../generics/generics.js";
// a -> Int // a -> Int
const a_to_Int = makeGeneric(a => fnType(a)(Int)); const a_to_Int = makeGeneric(a => fnType(a)(Int));

View file

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

View file

@ -3,7 +3,7 @@ import { makeGeneric } from "../generics/generics.js";
import { Double, Int } from "../primitives/types.js"; import { Double, Int } from "../primitives/types.js";
import { fnType } from "../structures/types.js"; import { fnType } from "../structures/types.js";
import { pretty } from '../util/pretty.js'; import { pretty } from '../util/pretty.js';
import { getMul, NumInstances } from "./num.js"; import { getMul, NumInstances } from "../typeclasses/num.js";
import { numDictType } from "./num_type.js"; import { numDictType } from "./num_type.js";
const square = numDict => x => getMul(numDict)(x)(x); const square = numDict => x => getMul(numDict)(x)(x);

View file

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

View file

@ -1,4 +1,4 @@
import { typedFnType } from "./types.js"; import { fnType, typedFnType } from "./types.js";
import { Char, GenericType, Type } from "../primitives/types.js"; import { Char, GenericType, Type } from "../primitives/types.js";
import { Int } from "../primitives/types.js"; import { Int } from "../primitives/types.js";
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
@ -11,6 +11,7 @@ const emptyListType = makeGeneric(a => lsType(a));
const get = ls => i => ls.l[i]; const get = ls => i => ls.l[i];
const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)}); const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)});
const push = ls => elem => ({l:ls.l.concat([elem])}); const push = ls => elem => ({l:ls.l.concat([elem])});
const map = ls => fn => ({ l: ls.l.map(elem => fn(elem)) });
export const String = lsType(Char); // alias export const String = lsType(Char); // alias
export const Module = lsType(Typed); export const Module = lsType(Typed);
@ -60,4 +61,14 @@ export const ModuleList = {l:[
(lsType(a)) (lsType(a))
) )
), GenericType), ), GenericType),
// [a] -> (a -> b) -> [b]
...typedFnType(map, fnType =>
makeGeneric((a, b) =>
fnType
(lsType(a))
(fnType
(fnType(a)(b))
(lsType(b))
)), GenericType),
]}; ]};

View file

@ -1,76 +0,0 @@
import { SymbolT, Type } from "../primitives/types.js";
import { makeTypeConstructor } from "../type_constructor.js";
import { Module, String } from "./list.js";
import { prodType, fnType, lsType } from "./types.js";
function capitalizeFirstLetter(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
export const createStruct = (typeVars, symbol, fields) => {
const makeConstructor = (remainingFields, obj={}) => {
if (remainingFields.length===0) {
return obj;
}
const {left: fieldName} = remainingFields[remainingFields.length-1];
return v => makeConstructor(
remainingFields.slice(0,-1),
Object.assign({[fieldName]: v}, obj));
};
const constructor = makeConstructor(fields);
const type = makeTypeConstructor(symbol)(typeVars.size);
const types = [ type ];
const recordFnType = inType => outType => {
const fnT = fnType(inType)(outType);
types.push(fnT);
return fnT;
}
const makeConstructorType = (remainingFields, type) => {
if (remainingFields.length===0) {
return type;
}
const {right: fieldType} = remainingFields[remainingFields.length-1];
return recordFnType(makeConstructorType(remainingFields.slice(0,-1)))(fieldType);
};
const constructorType = makeConstructorType(fields);
const functions = [
["constructor", constructor, constructorType],
...fields.map(({left: fieldName, right: fieldType}) => {
const getterName = 'get'+capitalizeFirstLetter(fieldName);
const getter = {
// stupid trick to give the JS-function a computed name.
// only important for debugging, so it says [Function: getAge] instead of [Function (anonymous)]:
[getterName]: obj => obj[fieldName],
}[getterName];
if (typeVars.has(fieldType)) {
// getterFnType = recordFnType(type)(fieldType)
}
const getterFnType = recordFnType(type)(fieldType);
return [fieldName, getter, getterFnType];
}),
];
const module = {l:[
{i: type, t: Type},
...functions.flatMap(([_, getter, getterFnType]) => [
{i: getter , t: getterFnType},
]),
...types.map(type => ({i: type, t: Type})),
]};
return {
module,
constructor,
functions: Object.fromEntries(functions),
};
};
export const createNominalADTModuleFnType =
fnType(SymbolT)
(fnType(lsType(prodType(String)(Type)))
(Module));

View file

@ -4,9 +4,9 @@ import { typedFnType } from "./types.js";
import { prodType } from "./types.js"; import { prodType } from "./types.js";
// In JS, all products are encoded in the same way: // In JS, all products are encoded in the same way:
const constructor = left => right => ({left, right}); export const constructorProduct = l => r => ({l, r});
const getLeft = product => product.left; export const getLeft = product => product.l;
const getRight = product => product.right; export const getRight = product => product.r;
export const ModuleProduct = {l: [ export const ModuleProduct = {l: [
// binary type constructor // binary type constructor
@ -21,7 +21,7 @@ export const ModuleProduct = {l: [
), ),
// a -> b -> (a, b) // a -> b -> (a, b)
...typedFnType(constructor, fnType => ...typedFnType(constructorProduct, fnType =>
makeGeneric((a, b) => makeGeneric((a, b) =>
fnType fnType
(a) (a)

View file

@ -1,39 +1,60 @@
import { setType, typedFnType } from "./types.js"; import { fnType, setType } from "./types.js";
import { Bool, GenericType, Type } from "../primitives/types.js"; import { Int } from "../primitives/types.js";
import { makeGeneric } from "../generics/generics.js"; import { makeGeneric } from "../generics/generics.js";
// 'normal' implementation import createRBTree from "functional-red-black-tree";
const emptySet = new Set(); import { inspect } from "node:util";
const emptySetType = makeGeneric(a => setType(a));
const has = set => elem => set.has(elem); export class RBTreeWrapper {
const add = set => elem => new Set([...set, elem]); constructor(tree) {
this.tree = tree;
}
[inspect.custom](depth, options, inspect) {
const entries = [];
this.tree.forEach((key,val) => {entries.push(`${inspect(key)} => ${inspect(val)}`);});
return `RBTree(${this.tree.length}) {${entries.join(', ')}}`;
}
}
// (a -> a -> Int) -> Set(a)
export const emptySet = compareFn => new RBTreeWrapper(createRBTree((x, y) => compareFn(x)(y)));
// const emptySetType = makeGeneric(a => fnType(fnType(a)(fnType(a)(Int)))(setType(a)));
export const has = set => key => set.tree.get(key) === true;
export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWrapper(set.tree.insert(key, true));
export const remove = set => key => new RBTreeWrapper(set.tree.remove(key));
export const forEach = set => fn => {
set.tree.forEach(key => { fn(key); });
};
export const ModuleSet = {l:[ export const ModuleSet = {l:[
// Type -> Type // // Type -> Type
...typedFnType(setType, fnType => // ...typedFnType(setType, fnType =>
fnType // fnType
/* in */ (Type) // /* in */ (Type)
/* out */ (Type) // /* out */ (Type)
), // ),
{i: emptySet , t: emptySetType}, // {i: emptySet , t: emptySetType},
{i: emptySetType, t: GenericType }, // {i: emptySetType, t: GenericType },
...typedFnType(has, fnType => // ...typedFnType(has, fnType =>
makeGeneric(a => // makeGeneric(a =>
fnType // fnType
/* in */ (setType(a)) // /* in */ (setType(a))
/* out */ (fnType // /* out */ (fnType
/* in */ (a) // /* in */ (a)
/* out */ (Bool) // /* out */ (Bool)
)), GenericType), // )), GenericType),
...typedFnType(add, fnType => // ...typedFnType(add, fnType =>
makeGeneric(a => // makeGeneric(a =>
fnType // fnType
/* in */ (setType(a)) // /* in */ (setType(a))
/* out */ (fnType // /* out */ (fnType
/* in */ (a) // /* in */ (a)
/* out */ (setType(a)) // /* out */ (setType(a))
)), GenericType), // )), GenericType),
]}; ]};

65
structures/struct.js Normal file
View file

@ -0,0 +1,65 @@
import { Unit } from "../primitives/types.js";
import { unit } from "../primitives/unit.js";
import { constructorProduct, getLeft, getRight } from "./product.js";
import { fnType, prodType } from "./types.js";
function capitalizeFirstLetter(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
// [{l: "x", r: Double}, {l: "y", r: Double}] => (Double × (Double × Unit))
export const structType = fields => {
if (fields.length === 0) {
return Unit;
}
const [field, ...rest] = fields;
const fieldType = getRight(field);
return prodType(fieldType)(structType(rest));
};
export const makeConstructor = fields => {
const internal = (nParams, ret) => {
if (nParams === 0) {
const result = ret(unit);
return result;
}
return nextParam => {
const wrappedName = 'wrapped_' + ret.name;
const newRet = {
[wrappedName]: inner => constructorProduct(nextParam)(ret(inner)),
}[wrappedName];
return internal(nParams-1, newRet);
}
};
const id = x => x;
return internal(fields.length, id);
};
export const makeConstructorType = type => fields => {
if (fields.length === 0) {
return type;
}
const [field, ...rest] = fields;
const fieldType = getRight(field);
return fnType(fieldType)(makeConstructorType(rest));
};
export const makeGetters = fields => {
if (fields.length === 0) {
return [];
}
const [field, ...rest] = fields;
const fieldName = getLeft(field);
const getterName = `get${capitalizeFirstLetter(fieldName)}`;
return [
{ [getterName]: obj => getLeft(obj) }[getterName],
...makeGetters(rest).map(getter => ({[getter.name]: obj => getter(getRight(obj))}[getter.name])),
];
};
export const makeGettersTypes = type => fields => {
return fields.map(field => {
const fieldType = getRight(field);
return fnType(type)(fieldType);
});
};

View file

@ -69,13 +69,13 @@ export function prettyT(type) {
} }
} }
if (type.symbol === symbolFunction) { if (type.symbol === symbolFunction) {
return `${prettyT(type.params[0])} -> ${prettyT(type.params[1])}`; return `(${prettyT(type.params[0])} -> ${prettyT(type.params[1])})`;
} }
if (type.symbol === symbolList) { if (type.symbol === symbolList) {
return `[${prettyT(type.params[0])}]`; return `[${prettyT(type.params[0])}]`;
} }
if (type.symbol === symbolProduct) { if (type.symbol === symbolProduct) {
return `(${prettyT(type.params[0])}, ${prettyT(type.params[1])})`; return `(${prettyT(type.params[0])} × ${prettyT(type.params[1])})`;
} }
if (type.symbol === symbolSum) { if (type.symbol === symbolSum) {
return `(${prettyT(type.params[0])} | ${prettyT(type.params[1])})`; return `(${prettyT(type.params[0])} | ${prettyT(type.params[1])})`;

View file

@ -1,68 +0,0 @@
import { makeGeneric } from "../generics/generics.js";
import { Bool } from "../primitives/types.js";
import { makeTypeConstructor } from "../type_constructor.js";
import { getEq } from "../typeclasses/eq.js";
import { eqDictType } from "../typeclasses/eq_type.js";
import { fnType, setType } from "./types.js";
const symbolVersioned = Symbol("Versioned");
export const versionedType = makeTypeConstructor(symbolVersioned)(1);
export const constructor = parents => alternatives => {
return { parents, alternatives };
}
const constructorType = makeGeneric(a =>
fnType
(setType(versionedType(a)))
(fnType
(setType(a))
(versionedType(a))
)
);
const initial = x => ({ parents: new Set(), alternatives: new Set(x) });
const initialFnType = makeGeneric(a => fnType(a)(versionedType(a)));
const eq = eqDict => vA => vB => {
return getEq(eqDict)(vA.value,vB.value) // compare values
&& (vA.parents.symmetricDifference(vB.parents).size === 0); // compare parents
}
// EqDict a -> Versioned a -> Versioned a -> Bool
const eqVersioned = makeGeneric(a =>
fnType
(eqDictType(a))
(fnType
(versionedType(a))
(fnType
(versionedType(a))
(Bool)
)
)
);
// EqDict a -> Versioned a -> Versioned a -> Versioned a -> Versioned a
export const mergeThreeWay = eqDict => vLCA => vA => vB => {
if (eq(eqDict)(vLCA)(vA)) {
return vB; // vB successor of vA
}
if (eq(eqDict)(vLCA)(vB)) {
return vA; // vA successor of vB
}
return mergeConcurrent(vLCA)(vA)(vB);
};
export const mergePrimitives = vA => vB => vA.union(vB);
// Versioned a -> Versioned a -> Versioned a
export const mergePrimitivesType = makeGeneric(a =>
fnType
(versionedType(a))
(fnType
(versionedType(a))
(versionedType(a))
)
);

View file

@ -24,7 +24,10 @@ const makeTypeConstructorInternal = (symbol, n_ary, params = []) => {
} }
else { else {
const m = new DefaultMap(typeParam => makeTypeConstructorInternal(symbol, n_ary - 1, params.concat([typeParam]))); const m = new DefaultMap(typeParam => makeTypeConstructorInternal(symbol, n_ary - 1, params.concat([typeParam])));
return typeParam => m.getdefault(typeParam, true); const fnName = 'make'+symbol.description+'Type';
return {
[fnName]: typeParam => m.getdefault(typeParam, true),
}[fnName];
} }
}; };

View file

@ -3,7 +3,7 @@ import { GenericType, SymbolT, Type, Unit } from "../primitives/types";
import { typedFnType } from "../structures/types"; import { typedFnType } from "../structures/types";
import { Bool, Byte, Char, Double, Int } from "../primitives/types"; import { Bool, Byte, Char, Double, Int } from "../primitives/types";
import { deepEqual } from "../util/util"; import { deepEqual } from "../util/util";
import { eqDictType } from "./eq_type"; import { eqDictType } from "./eq_dict";
export const getEq = numDict => numDict.eq; export const getEq = numDict => numDict.eq;

4
typeclasses/eq_dict.js Normal file
View file

@ -0,0 +1,4 @@
import { makeTypeConstructor } from "../type_constructor.js";
const eqDictSymbol = Symbol('EqDict');
export const eqDictType = makeTypeConstructor(eqDictSymbol)(1);

View file

@ -1,8 +0,0 @@
import { DefaultMap } from "../util/defaultmap.js";
const eqDictSymbol = Symbol('EqDict');
const eqDictTypeRegistry = new DefaultMap(a => ({
symbol: eqDictSymbol,
params: [a],
}));
export const eqDictType = a => eqDictTypeRegistry.getdefault(a, true);

4
typeclasses/num_dict.js Normal file
View file

@ -0,0 +1,4 @@
import { makeTypeConstructor } from "../type_constructor.js";
const numDictSymbol = Symbol("NumDict");
export const numDictType = makeTypeConstructor(numDictSymbol)(1);

View file

@ -1,8 +0,0 @@
import { DefaultMap } from "../util/defaultmap.js";
const numDictSymbol = Symbol("NumDict");
const numDictTypeRegistry = new DefaultMap(a => ({
symbol: numDictSymbol,
params: [a],
}));
export const numDictType = a => numDictTypeRegistry.getdefault(a, true);

9
typeclasses/show.js Normal file
View file

@ -0,0 +1,9 @@
import { cart2Str, NPoint2DCartesian, NPoint2DPolar, polar2Str } from "../lib/point.js";
import { Type } from "../primitives/types.js";
import { prettyT } from "../structures/types.js";
export const ShowInstances = new Map([
[Type , {show: prettyT}],
[NPoint2DCartesian, {show: cart2Str}],
[NPoint2DPolar , {show: polar2Str}],
]);

5
typeclasses/show_dict.js Normal file
View file

@ -0,0 +1,5 @@
import { makeTypeConstructor } from "../type_constructor.js";
const showDictSymbol = Symbol('ShowDict');
export const showDictType = makeTypeConstructor(showDictSymbol)(1);

View file

@ -1,11 +1,7 @@
import { typedFnType } from "./structures/types.js"; import { typedFnType } from "./structures/types.js";
import { Type } from "./primitives/types.js"; import { Any, Type } from "./primitives/types.js";
import { makeTypeConstructor } from "./type_constructor.js"; import { makeTypeConstructor } from "./type_constructor.js";
// Everything is (implicitly) typed by the Any type.
const symbolAny = Symbol('Any');
export const Any = makeTypeConstructor(symbolAny)(0);
// A type-link, connecting a value to its Type. // A type-link, connecting a value to its Type.
const symbolTyped = Symbol('Typed'); const symbolTyped = Symbol('Typed');
export const Typed = makeTypeConstructor(symbolTyped)(0); export const Typed = makeTypeConstructor(symbolTyped)(0);

View file

@ -1,6 +1,5 @@
import { inspect } from 'node:util'; import { inspect } from 'node:util';
export function pretty(obj) { export function pretty(obj) {
return inspect(obj, { colors: true, depth: null }); return inspect(obj, { colors: true, depth: null, breakLength: 120 });
} }

View file

@ -12,6 +12,20 @@ export function deepEqual(a, b) {
} }
return true; return true;
} }
if (a instanceof Set) {
if (!(b instanceof Set)) {
return false;
}
if (a.size !== b.size) {
return false;
}
for (const entry of a) {
if (!b.has(entry)) {
return false;
}
}
return true;
}
const keysA = Object.keys(a); const keysA = Object.keys(a);
const keysB = Object.keys(b); const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false; if (keysA.length !== keysB.length) return false;

96
versioning/slot.js Normal file
View file

@ -0,0 +1,96 @@
import { add, emptySet, forEach } from "../structures/set.js";
import { deepEqual } from "../util/util.js";
import {inspect} from "node:util";
// UUID -> Value<a> -> Slot<a>
export const newSlot = uuid => value => ({
overwrites: uuid,
value,
depth: 1,
[inspect.custom]: (depth, options, inspect) => `newSlot(${inspect(uuid)}, ${inspect(value)})`,
});
export const compareSlots = compareElems => slotA => slotB => {
if (slotA.depth < slotB.depth) {
return -1;
}
if (slotB.depth < slotA.depth) {
return 1;
}
return compareValues(compareElems)(slotA.value)(slotB.value);
};
export const compareValues = compareElems => valA => valB => {
if (valA.kind < valB.kind) {
return -1;
}
if (valB.kind < valA.kind) {
return 1;
}
if (valA.kind === "literal") {
return compareElems(valA.out)(valB.out);
}
if (valA.kind === "read") {
return compareSlots(compareElems)(valA.slot)(valB.slot);
}
if (valA.kind === "transformation") {
const cmpIn = compareValues(compareElems)(valA.in)(valB.in);
if (cmpIn !== 0) {
return cmpIn;
}
return compareValues(compareElems)(valA.fn)(valB.fn);
}
};
// Slot<a> -> Value<a> -> Slot<a>
export const overwrite = slot => value => ({
overwrites: slot,
value,
depth: slot.depth + 1,
// [inspect.custom]: (depth, options, inspect) => `overwrite(${inspect(slot)}, ${inspect(value)})`,
});
const findLCA = slotA => slotB => {
if (deepEqual(slotA, slotB)) {
return slotA;
}
if (slotA.depth > slotB.depth) {
return findLCA(slotA.overwrites)(slotB)
}
else {
return findLCA(slotB.overwrites)(slotA)
}
};
// Slot<a> -> Slot<a> -> MergeResult<a>
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]);
};
// MergeResult<a> -> MergeResult<a> -> MergeResult<a>
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;
};

95
versioning/value.js Normal file
View file

@ -0,0 +1,95 @@
import { deepEqual } from "../util/util.js";
import {inspect} from "node:util";
// A Value is either:
// - a literal, without any dependencies.
// - read from a slot. the Value then has a read-dependency on that slot.
// - a transformation of another Value, by a function. the function is also a Value.
// 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: "transformation",
in: input,
fn,
out: output,
// [inspect.custom]: _inspect,
};
}
};
// Value<a> -> Set<Slot<Any>>
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 === "transformation") {
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 === "transformation") {
compare(value.fn.out(value.in.out),
value.out, "transformation");
success &= verifyValue(value.in, indent + 1);
success &= verifyValue(value.fn, indent + 1);
}
return success;
};