diff --git a/lib/point.js b/lib/point.js index 300581b..89f8925 100644 --- a/lib/point.js +++ b/lib/point.js @@ -1,10 +1,11 @@ import { Type } from "../primitives/types.js"; import { typedFnType } from "../structures/types.js" import { Double } from "../primitives/types.js"; +import { makeTypeConstructor } from "../type_constructor.js"; // Create nominal types -export const PointCartesian2D = { symbol: Symbol('PointCartesian2D'), params: [] }; -export const PointPolar2D = { symbol: Symbol('PointPolar2D') , params: [] }; +export const PointCartesian2D = makeTypeConstructor(Symbol('PointCartesian2D'))(0); +export const PointPolar2D = makeTypeConstructor(Symbol('PointPolar2D'))(0); export const cart2polar = ({x, y}) => { const r = Math.sqrt(x*x + y*y); diff --git a/lib/point_structural.js b/lib/point_structural.js new file mode 100644 index 0000000..c162c74 --- /dev/null +++ b/lib/point_structural.js @@ -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))), +]}; diff --git a/lib/symbol.js b/lib/symbol.js index bbaf586..1bb94e8 100644 --- a/lib/symbol.js +++ b/lib/symbol.js @@ -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 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:[ {i: SymbolT, t: Type}, diff --git a/primitives/generic_type.js b/primitives/generic_type.js index 1f029aa..c9a0065 100644 --- a/primitives/generic_type.js +++ b/primitives/generic_type.js @@ -1,6 +1,6 @@ import { constructorLeft, constructorRight } from "../structures/sum.js"; -import { fnType, setType, sumType, typedFnType } from "../structures/types.js"; -import { GenericType, SymbolT, Type, Unit } from "./types.js"; +import { setType, sumType, typedFnType } from "../structures/types.js"; +import { Any, GenericType, SymbolT, Type, Unit } from "./types.js"; import { unit } from "./unit.js"; export const getType = genericType => genericType.type; @@ -11,11 +11,11 @@ export const toNonGeneric = genericType => (genericType.typeVars.size === 0) : constructorLeft(unit); 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))), ]}; diff --git a/primitives/types.js b/primitives/types.js index 974497d..5f12473 100644 --- a/primitives/types.js +++ b/primitives/types.js @@ -10,6 +10,7 @@ const SymbolChar = Symbol('Char'); const SymbolUnit = Symbol('Unit'); const SymbolSymbol = Symbol('Symbol'); const SymbolType = Symbol('Type'); +const symbolAny = Symbol('Any'); const SymbolGenericType = Symbol('GenericType'); export const Int = makeTypeConstructor(SymbolInt)(0); @@ -23,10 +24,13 @@ export const Unit = makeTypeConstructor(SymbolUnit)(0); export const SymbolT = makeTypeConstructor(SymbolSymbol)(0); +// Types are typed by Any export const Type = makeTypeConstructor(SymbolType)(0); export const GenericType = makeTypeConstructor(SymbolGenericType)(0); +// Everything is typed by Any +export const Any = makeTypeConstructor(symbolAny)(0); export const ModuleSymbols = {l:[ {i: SymbolInt , t: SymbolT}, @@ -39,3 +43,5 @@ export const ModuleSymbols = {l:[ {i: SymbolType , t: SymbolT}, {i: SymbolGenericType, t: SymbolT}, ]}; + + diff --git a/progress.txt b/progress.txt index dc9888b..2e6eb9d 100644 --- a/progress.txt +++ b/progress.txt @@ -1,72 +1,24 @@ -status: +done: - everything is properly typed, up to the meta-circular level - primitives - structures: list, product, sum - can compose structures, e.g., create list of list of product of sum of ... - list type is specialized for ListOfByte to use Uint8Array - - generics currently implemented in two ways: - 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. - + can compose structures, e.g., create list of list of product of sum of ... + list type is specialized for ListOfByte to use Uint8Array + - generic types and type inferencing todo: - - rename Type to a NominalType? - const nominalType = (name, params) => ({left: name, right: params}); - type of every nominal type is (String, [Type]) + - to support sets of slots, need comparison of slots + => comparison of values + => 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'? - - - 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? - diff --git a/generics/generics.test.js b/scripts/generics.js similarity index 94% rename from generics/generics.test.js rename to scripts/generics.js index 3dcffdf..cd0b74b 100644 --- a/generics/generics.test.js +++ b/scripts/generics.js @@ -1,6 +1,6 @@ import { Bool, Int } from "../primitives/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 const a_to_Int = makeGeneric(a => fnType(a)(Int)); diff --git a/scripts/main.js b/scripts/main.js index 597ebe7..740fd44 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -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]; diff --git a/typeclasses/num.test.js b/scripts/num.js similarity index 93% rename from typeclasses/num.test.js rename to scripts/num.js index e2200ba..eaa4a67 100644 --- a/typeclasses/num.test.js +++ b/scripts/num.js @@ -3,7 +3,7 @@ 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 "./num.js"; +import { getMul, NumInstances } from "../typeclasses/num.js"; import { numDictType } from "./num_type.js"; const square = numDict => x => getMul(numDict)(x)(x); diff --git a/scripts/rbtree_bench.js b/scripts/rbtree_bench.js index faae879..dda9889 100644 --- a/scripts/rbtree_bench.js +++ b/scripts/rbtree_bench.js @@ -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 diff --git a/scripts/versioning.js b/scripts/versioning.js new file mode 100644 index 0000000..aa0bd1e --- /dev/null +++ b/scripts/versioning.js @@ -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 +// 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(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))); \ No newline at end of file diff --git a/scripts/versioning2.js b/scripts/versioning2.js deleted file mode 100644 index 2d21502..0000000 --- a/scripts/versioning2.js +++ /dev/null @@ -1,130 +0,0 @@ -import { pretty } from "../util/pretty.js"; -import { deepEqual } from "../util/util.js"; - -// UUID -> Computation -> Slot -const newSlot = uuid => computation => ({ - overwrites: uuid, - computation, - // depth: 1, -}); - -// a -> Computation -const newValue = val => ({ - kind: "value", - out: val, -}); - -// Slot -> Computation -const read = slot => ({ - kind: "read", - slot, - out: slot.computation.out, -}); - -// Computation -> Computation b> -> Computation -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)); diff --git a/structures/list.js b/structures/list.js index 60a6cd4..eab85f0 100644 --- a/structures/list.js +++ b/structures/list.js @@ -1,4 +1,4 @@ -import { typedFnType } from "./types.js"; +import { fnType, typedFnType } from "./types.js"; import { Char, GenericType, Type } from "../primitives/types.js"; import { Int } from "../primitives/types.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 put = ls => i => elem => ({l: ls.l.with(Number(i), 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 Module = lsType(Typed); @@ -60,4 +61,14 @@ export const ModuleList = {l:[ (lsType(a)) ) ), GenericType), + + // [a] -> (a -> b) -> [b] + ...typedFnType(map, fnType => + makeGeneric((a, b) => + fnType + (lsType(a)) + (fnType + (fnType(a)(b)) + (lsType(b)) + )), GenericType), ]}; diff --git a/structures/nominal.js b/structures/nominal.js deleted file mode 100644 index b0105da..0000000 --- a/structures/nominal.js +++ /dev/null @@ -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)); diff --git a/structures/product.js b/structures/product.js index 166738f..f5d9bc3 100644 --- a/structures/product.js +++ b/structures/product.js @@ -4,9 +4,9 @@ import { typedFnType } from "./types.js"; import { prodType } from "./types.js"; // In JS, all products are encoded in the same way: -const constructor = left => right => ({left, right}); -const getLeft = product => product.left; -const getRight = product => product.right; +export const constructorProduct = l => r => ({l, r}); +export const getLeft = product => product.l; +export const getRight = product => product.r; export const ModuleProduct = {l: [ // binary type constructor @@ -21,7 +21,7 @@ export const ModuleProduct = {l: [ ), // a -> b -> (a, b) - ...typedFnType(constructor, fnType => + ...typedFnType(constructorProduct, fnType => makeGeneric((a, b) => fnType (a) diff --git a/structures/set.js b/structures/set.js index dc7ac94..c1df019 100644 --- a/structures/set.js +++ b/structures/set.js @@ -1,39 +1,60 @@ -import { setType, typedFnType } from "./types.js"; -import { Bool, GenericType, Type } from "../primitives/types.js"; +import { fnType, setType } from "./types.js"; +import { Int } from "../primitives/types.js"; import { makeGeneric } from "../generics/generics.js"; -// 'normal' implementation -const emptySet = new Set(); -const emptySetType = makeGeneric(a => setType(a)); -const has = set => elem => set.has(elem); -const add = set => elem => new Set([...set, elem]); +import createRBTree from "functional-red-black-tree"; +import { inspect } from "node:util"; + +export class RBTreeWrapper { + 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:[ - // Type -> Type - ...typedFnType(setType, fnType => - fnType - /* in */ (Type) - /* out */ (Type) - ), + // // Type -> Type + // ...typedFnType(setType, fnType => + // fnType + // /* in */ (Type) + // /* out */ (Type) + // ), - {i: emptySet , t: emptySetType}, - {i: emptySetType, t: GenericType }, + // {i: emptySet , t: emptySetType}, + // {i: emptySetType, t: GenericType }, - ...typedFnType(has, fnType => - makeGeneric(a => - fnType - /* in */ (setType(a)) - /* out */ (fnType - /* in */ (a) - /* out */ (Bool) - )), GenericType), + // ...typedFnType(has, fnType => + // makeGeneric(a => + // fnType + // /* in */ (setType(a)) + // /* out */ (fnType + // /* in */ (a) + // /* out */ (Bool) + // )), GenericType), - ...typedFnType(add, fnType => - makeGeneric(a => - fnType - /* in */ (setType(a)) - /* out */ (fnType - /* in */ (a) - /* out */ (setType(a)) - )), GenericType), + // ...typedFnType(add, fnType => + // makeGeneric(a => + // fnType + // /* in */ (setType(a)) + // /* out */ (fnType + // /* in */ (a) + // /* out */ (setType(a)) + // )), GenericType), ]}; diff --git a/structures/struct.js b/structures/struct.js new file mode 100644 index 0000000..aaeb588 --- /dev/null +++ b/structures/struct.js @@ -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); + }); +}; diff --git a/structures/types.js b/structures/types.js index d64d97b..6d38e17 100644 --- a/structures/types.js +++ b/structures/types.js @@ -69,13 +69,13 @@ export function prettyT(type) { } } 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) { return `[${prettyT(type.params[0])}]`; } 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) { return `(${prettyT(type.params[0])} | ${prettyT(type.params[1])})`; diff --git a/structures/versioned.js b/structures/versioned.js deleted file mode 100644 index d177fb0..0000000 --- a/structures/versioned.js +++ /dev/null @@ -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)) - ) -); \ No newline at end of file diff --git a/type_constructor.js b/type_constructor.js index 52ed83b..93bf1ed 100644 --- a/type_constructor.js +++ b/type_constructor.js @@ -24,7 +24,10 @@ const makeTypeConstructorInternal = (symbol, n_ary, params = []) => { } else { 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]; } }; diff --git a/typeclasses/eq.js b/typeclasses/eq.js index 5aac1e2..37b571b 100644 --- a/typeclasses/eq.js +++ b/typeclasses/eq.js @@ -3,7 +3,7 @@ import { GenericType, SymbolT, Type, Unit } from "../primitives/types"; import { typedFnType } from "../structures/types"; import { Bool, Byte, Char, Double, Int } from "../primitives/types"; import { deepEqual } from "../util/util"; -import { eqDictType } from "./eq_type"; +import { eqDictType } from "./eq_dict"; export const getEq = numDict => numDict.eq; diff --git a/typeclasses/eq_dict.js b/typeclasses/eq_dict.js new file mode 100644 index 0000000..7fbd01f --- /dev/null +++ b/typeclasses/eq_dict.js @@ -0,0 +1,4 @@ +import { makeTypeConstructor } from "../type_constructor.js"; + +const eqDictSymbol = Symbol('EqDict'); +export const eqDictType = makeTypeConstructor(eqDictSymbol)(1); \ No newline at end of file diff --git a/typeclasses/eq_type.js b/typeclasses/eq_type.js deleted file mode 100644 index 309a46d..0000000 --- a/typeclasses/eq_type.js +++ /dev/null @@ -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); diff --git a/typeclasses/num_dict.js b/typeclasses/num_dict.js new file mode 100644 index 0000000..bf59390 --- /dev/null +++ b/typeclasses/num_dict.js @@ -0,0 +1,4 @@ +import { makeTypeConstructor } from "../type_constructor.js"; + +const numDictSymbol = Symbol("NumDict"); +export const numDictType = makeTypeConstructor(numDictSymbol)(1); diff --git a/typeclasses/num_type.js b/typeclasses/num_type.js deleted file mode 100644 index db6693d..0000000 --- a/typeclasses/num_type.js +++ /dev/null @@ -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); diff --git a/typeclasses/show.js b/typeclasses/show.js new file mode 100644 index 0000000..9d9b6df --- /dev/null +++ b/typeclasses/show.js @@ -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}], +]); diff --git a/typeclasses/show_dict.js b/typeclasses/show_dict.js new file mode 100644 index 0000000..83166f0 --- /dev/null +++ b/typeclasses/show_dict.js @@ -0,0 +1,5 @@ +import { makeTypeConstructor } from "../type_constructor.js"; + +const showDictSymbol = Symbol('ShowDict'); +export const showDictType = makeTypeConstructor(showDictSymbol)(1); + diff --git a/typed.js b/typed.js index 4542870..b3a1d71 100644 --- a/typed.js +++ b/typed.js @@ -1,11 +1,7 @@ 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"; -// 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. const symbolTyped = Symbol('Typed'); export const Typed = makeTypeConstructor(symbolTyped)(0); diff --git a/util/pretty.js b/util/pretty.js index 645f3fa..9b0681b 100644 --- a/util/pretty.js +++ b/util/pretty.js @@ -1,6 +1,5 @@ import { inspect } from 'node:util'; - export function pretty(obj) { - return inspect(obj, { colors: true, depth: null }); + return inspect(obj, { colors: true, depth: null, breakLength: 120 }); } diff --git a/util/util.js b/util/util.js index 3d6a002..db37406 100644 --- a/util/util.js +++ b/util/util.js @@ -12,6 +12,20 @@ export function deepEqual(a, b) { } 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 keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; diff --git a/versioning/slot.js b/versioning/slot.js new file mode 100644 index 0000000..587626b --- /dev/null +++ b/versioning/slot.js @@ -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 -> Slot +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 -> Value -> Slot +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 -> Slot -> MergeResult +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 -> MergeResult -> MergeResult +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; +}; diff --git a/versioning/value.js b/versioning/value.js new file mode 100644 index 0000000..3709bc9 --- /dev/null +++ b/versioning/value.js @@ -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 +export const newLiteral = val => ({ + kind: "literal", + out: val, + [inspect.custom]: (depth, options, inspect) => `newLiteral(${inspect(val)})`, +}); + +// Slot -> Value +export const read = slot => ({ + kind: "read", + slot, + out: slot.value.out, + [inspect.custom]: (depth, options, inspect) => `read(${inspect(slot)})`, +}); + +// Value -> Value b> -> Value +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 -> Set> +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; +};