branching and very basic merging of slots
This commit is contained in:
parent
614e6c0fdb
commit
3978f7f835
32 changed files with 684 additions and 420 deletions
|
|
@ -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
140
lib/point_structural.js
Normal 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))),
|
||||||
|
]};
|
||||||
|
|
@ -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},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
]};
|
]};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
78
progress.txt
78
progress.txt
|
|
@ -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?
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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
122
scripts/versioning.js
Normal 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)));
|
||||||
|
|
@ -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));
|
|
||||||
|
|
@ -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),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -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));
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
65
structures/struct.js
Normal 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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -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])})`;
|
||||||
|
|
|
||||||
|
|
@ -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))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
@ -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];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
4
typeclasses/eq_dict.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { makeTypeConstructor } from "../type_constructor.js";
|
||||||
|
|
||||||
|
const eqDictSymbol = Symbol('EqDict');
|
||||||
|
export const eqDictType = makeTypeConstructor(eqDictSymbol)(1);
|
||||||
|
|
@ -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
4
typeclasses/num_dict.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { makeTypeConstructor } from "../type_constructor.js";
|
||||||
|
|
||||||
|
const numDictSymbol = Symbol("NumDict");
|
||||||
|
export const numDictType = makeTypeConstructor(numDictSymbol)(1);
|
||||||
|
|
@ -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
9
typeclasses/show.js
Normal 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
5
typeclasses/show_dict.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { makeTypeConstructor } from "../type_constructor.js";
|
||||||
|
|
||||||
|
const showDictSymbol = Symbol('ShowDict');
|
||||||
|
export const showDictType = makeTypeConstructor(showDictSymbol)(1);
|
||||||
|
|
||||||
6
typed.js
6
typed.js
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
util/util.js
14
util/util.js
|
|
@ -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
96
versioning/slot.js
Normal 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
95
versioning/value.js
Normal 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;
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue