diff --git a/compare/primitives.js b/compare/primitives.js
index d5e2638..5e15119 100644
--- a/compare/primitives.js
+++ b/compare/primitives.js
@@ -26,4 +26,4 @@ export const compareUnits = x => y => 0;
// Note: dirty assumption that every symbol has unique description.
// This will be fixed once we move from symbols to real UUIDs.
-export const compareSymbols = a => b => (a !== b) && compareStrings(a.description)(b.description);
+export const compareSymbols = a => b => Number(a !== b) && compareStrings(a.description)(b.description);
diff --git a/compare/type.js b/compare/type.js
index a91f841..fb3a4dc 100644
--- a/compare/type.js
+++ b/compare/type.js
@@ -1,7 +1,38 @@
import { getParams, getSymbol } from "../type_constructor.js";
-import { compareSymbols } from "./primitives.js";
+import { compareBools, compareSymbols } from "./primitives.js";
import { compareLists } from "./structures.js";
-export const compareTypes = x => y =>
- compareSymbols(getSymbol(x))(getSymbol(y))
- || compareLists(compareTypes)(getParams(x))(getParams(y));
+const __compareTypes = state => typeX => typeY => {
+ // tagX and tagY: just something unique & deterministic that can serve as JS map key
+ const tagX = state.nextTag++;
+ const tagY = state.nextTag++;
+ state.tagsX.add(tagX);
+ state.tagsY.add(tagY);
+ state.comparing.set(tagX, tagY);
+ return compareSymbols(getSymbol(typeX))(getSymbol(typeY))
+ || compareLists
+ (paramOfX => paramOfY => {
+ const pX = paramOfX(tagX);
+ const pY = paramOfY(tagY);
+ return compareBools(state.tagsX.has(pX))(state.tagsY.has(pY))
+ || (() => {
+ if (state.tagsX.has(pX)) {
+ // both sub-types have been visited already in an enclosing call
+ // if they were being compared in the same enclosing call, we assume they are equal!
+ // (we cannot compare them, that would result in endless recursion)
+ return compareSymbols(state.comparing.get(pX))(pY);
+ }
+ // none have been visited -> recursively compare
+ return __compareTypes(state)(pX)(pY);
+ })();
+ })
+ (getParams(typeX))
+ (getParams(typeY));
+};
+
+export const compareTypes = typeX => typeY => __compareTypes({
+ tagsX: new Set(),
+ tagsY: new Set(),
+ comparing: new Map(),
+ nextTag: 0,
+})(typeX)(typeY);
diff --git a/examples/compare_types.js b/examples/compare_types.js
new file mode 100644
index 0000000..d4cbcbe
--- /dev/null
+++ b/examples/compare_types.js
@@ -0,0 +1,87 @@
+import { compareTypes } from "../compare/type.js";
+import { makeGeneric, substitute, unify } from "../generics/generics.js";
+import { Double, Int, Unit } from "../primitives/types.js";
+import { fnType, lsType, prodType, setType, sumType } from "../structures/types.js";
+import { prettyGenT, prettyT } from "../util/pretty.js";
+
+// some recursive types:
+
+const listOfSetOfSelf = lsType(self => setType(_ => self));
+
+const makeLinkedList = elementType => sumType
+ (self => prodType
+ (_ => elementType)
+ (_ => self))
+ (_ => Unit);
+
+const linkedListOfInt = makeLinkedList(Int);
+const linkedListOfDouble = makeLinkedList(Double);
+
+// some generic types
+
+const genericFunction = makeGeneric((a,b) => fnType(_ => a)(_ => b));
+const genericLinkedList = makeGeneric(a => makeLinkedList(a));
+
+// pretty-printing of recursive types:
+
+console.log(prettyT(listOfSetOfSelf)); // #0[{#0}]
+console.log(prettyT(linkedListOfInt)); // #0((Int ⨯ #0) + Unit)
+console.log(prettyGenT(genericFunction)); // ∀a,b: (a -> b)
+console.log(prettyGenT(genericLinkedList)); // ∀a: #0((a ⨯ #0) + Unit)
+
+// comparison
+
+console.log(compareTypes(listOfSetOfSelf)(listOfSetOfSelf)) // 0
+console.log(compareTypes(linkedListOfInt)(linkedListOfInt)) // 0
+console.log(compareTypes(linkedListOfInt)(linkedListOfDouble)) // 1
+console.log(compareTypes(linkedListOfDouble)(linkedListOfInt)) // -1
+console.log(compareTypes(linkedListOfDouble)(linkedListOfDouble)) // 0
+console.log(compareTypes(linkedListOfDouble)(listOfSetOfSelf)) // 1
+console.log(compareTypes(listOfSetOfSelf)(linkedListOfDouble)) // -1
+
+
+const genericList = makeGeneric(a => lsType(_ => a));
+const intList = lsType(_ => Int);
+
+console.log(prettyGenT(genericList)); // ∀a: [a]
+
+// substitution of type parameters
+
+const substituted = substitute(
+ genericList.type,
+ new Map([[
+ genericList.typeVars.keys().next().value,
+ Int,
+ ]])
+);
+
+console.log(prettyT(substituted)); // [Int]
+
+// substitution (recursive this time)
+
+console.log("recursive substitution")
+console.log(prettyT(substitute(
+ genericLinkedList.type,
+ new Map([[
+ genericLinkedList.typeVars.keys().next().value,
+ Int,
+ ]])
+))); // #0((Int ⨯ #0) + Unit)
+
+// unification (simple case)
+
+const {typeVars, type} = unify(
+ genericList,
+ makeGeneric(() => intList));
+
+console.log(prettyT(type)); // [Int]
+
+// unification (recursive case)
+
+console.log("complex case...")
+
+const unified = unify(
+ genericLinkedList,
+ makeGeneric(() => linkedListOfInt));
+
+console.log(prettyGenT(unified)); // ∀: #0((Int ⨯ #0) + Unit)
diff --git a/examples/enum.js b/examples/enum.js
index c6231e2..43223f4 100644
--- a/examples/enum.js
+++ b/examples/enum.js
@@ -7,7 +7,7 @@ import { lsType, prettyT } from "../structures/types.js";
const variants = [
newProduct("price")(Int),
- newProduct("prices")(lsType(Int)),
+ newProduct("prices")(lsType(() =>Int)),
newProduct("not_found")(Unit),
];
diff --git a/examples/generics.js b/examples/generics.js
index cd0b74b..e307537 100644
--- a/examples/generics.js
+++ b/examples/generics.js
@@ -1,38 +1,41 @@
import { Bool, Int } from "../primitives/types.js";
-import { fnType, lsType, prettyT } from "../structures/types.js";
+import { fnType, lsType } from "../structures/types.js";
import { assign, makeGeneric, unify } from "../generics/generics.js";
+import { prettyGenT, prettyT } from "../util/pretty.js";
// a -> Int
-const a_to_Int = makeGeneric(a => fnType(a)(Int));
+const a_to_Int = makeGeneric(a => fnType(() => a)(() => Int));
+console.log((prettyGenT(a_to_Int))); // ∀a: (a -> Int)
// Bool -> Int
-const Bool_to_Int = makeGeneric(() => fnType(lsType(Bool))(Int));
+const Bool_to_Int = makeGeneric(() => fnType(() => lsType(() =>Bool))(() => Int));
+console.log((prettyGenT(Bool_to_Int))); // ∀: ([Bool] -> Int)
console.log("should be: [Bool] -> Int")
-console.log(prettyT(unify(a_to_Int, Bool_to_Int)));
+console.log(prettyGenT(unify(a_to_Int, Bool_to_Int)));
// (a -> a) -> b
-const fnType2 = makeGeneric((a,b) => fnType(fnType(a)(a))(b));
+const fnType2 = makeGeneric((a,b) => fnType(() => fnType(a)(a))(() => b));
// (Bool -> Bool) -> a
-const fnType3 = makeGeneric(a => fnType(fnType(Bool)(Bool))(a));
+const fnType3 = makeGeneric(a => fnType(() => fnType(Bool)(Bool))(() => a));
console.log("should be: (Bool -> Bool) -> a");
console.log(prettyT(unify(fnType2, fnType3)));
// (a -> b) -> [a] -> [b]
const mapFnType = makeGeneric((a,b) =>
fnType
- (fnType(a)(b))
- (fnType(lsType(a))(lsType(b))))
+ (fnType(() => a)(() => b))
+ (fnType(() => lsType(() =>a))(() => lsType(() =>b))))
// a -> a
const idFnType = makeGeneric((_,__,c) =>
- fnType(c)(c));
+ fnType(() => c)(() => c));
console.log("should be: [c] -> [c]");
console.log(prettyT(assign(mapFnType, idFnType)));
// (a -> Int) -> [a] -> a
const weirdFnType = makeGeneric(a =>
fnType
- (fnType(a)(Int))
+ (fnType(() => a)(() => Int))
(fnType
- (lsType(a))
+ (lsType(() =>a))
(a)))
// we call this function with parameter of type (b -> b) ...
// giving these substitutions:
diff --git a/examples/int_or_bool.js b/examples/int_or_bool.js
index 9b29e79..4f773f9 100644
--- a/examples/int_or_bool.js
+++ b/examples/int_or_bool.js
@@ -4,14 +4,14 @@ import { newLeft, newRight, match } from "../structures/sum.js";
import { fnType, sumType } from "../structures/types.js";
import { pretty } from '../util/pretty.js';
-const IntOrBool = sumType(Int)(Bool);
+const IntOrBool = sumType(() => Int)(() => Bool);
// console.log(int5);
console.log(pretty(unify(
makeGeneric(() => IntOrBool),
- makeGeneric(a => sumType(Int)(a)),
+ makeGeneric(a => sumType(() => Int)(() => a)),
)));
const cipFunction = (x) => {
@@ -38,7 +38,7 @@ const typeAtCallSite = assign(
makeGeneric((a, b) =>
fnType
(a)
- (sumType(a)(b))
+ (sumType(() => a)(() => b))
),
makeGeneric(() => Int));
console.log(pretty(typeAtCallSite));
diff --git a/examples/num.js b/examples/num.js
index eaa4a67..0fe79f4 100644
--- a/examples/num.js
+++ b/examples/num.js
@@ -12,7 +12,7 @@ const square = numDict => x => getMul(numDict)(x)(x);
const squareFnType = makeGeneric(a =>
fnType
(numDictType(a))
- (fnType(a)(a))
+ (fnType(() => a)(() => a))
);
console.log("should be: Int -> Int");
diff --git a/examples/prompt.js b/examples/prompt.js
index c17e27d..f2cea2b 100644
--- a/examples/prompt.js
+++ b/examples/prompt.js
@@ -6,7 +6,7 @@ import { isFunction } 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 "../primitives/types.js";
+import { Top } from "../primitives/types.js";
import { assignFn, makeGeneric, onlyOccurring } from '../generics/generics.js';
import { prettyT } from '../util/pretty.js';
@@ -48,7 +48,7 @@ class Context {
// console.log(strI, '::', strT);
this.types.getdefault(i, true).add(t);
- this.types.getdefault(i, true).add(Any);
+ this.types.getdefault(i, true).add(Top);
if (t.typeVars) {
// console.log("generic:", prettyT(t));
this.types.getdefault(t, true).add(GenericType);
@@ -59,7 +59,7 @@ class Context {
}
this.instances.getdefault(t, true).add(i);
- this.instances.getdefault(Any, true).add(i);
+ this.instances.getdefault(Top, true).add(i);
}
const addIfFunctionType = (t, originalT, add) => {
if (isFunction(t)) {
diff --git a/generics/generics.js b/generics/generics.js
index 5a987dc..9b32d57 100644
--- a/generics/generics.js
+++ b/generics/generics.js
@@ -1,13 +1,12 @@
import { eqType } from "../primitives/type.js";
import { zip } from "../util/util.js";
-import { pretty } from '../util/pretty.js';
-import { prettyT } from "../util/pretty.js";
+import { pretty, prettyT } from '../util/pretty.js';
// constructor for generic types
// for instance, the type:
// ∀a: a -> a -> Bool
// is created by
-// makeGeneric(a => fnType(a)(fnType(a)(Bool)))
+// makeGeneric(a => fnType(() => a)(() => fnType(() => a)(() => Bool)))
export const makeGeneric = callback => {
// type variables to make available:
const typeVars = ['a', 'b', 'c', 'd', 'e'].map(Symbol);
@@ -20,14 +19,24 @@ export const onlyOccurring = (type, typeVars) => ({
type,
});
-// From the given set of type variables, return only those that occur in the given type.
-export const occurring = (type, typeVars) => {
- // console.log("occurring", type);
+const __occurring = state => typeVars => type => {
if (typeVars.has(type)) {
- // type IS a type variable:
return new Set([type]);
}
- return new Set(type.params.flatMap(p => [...occurring(p, typeVars)]));
+ const tag = state.nextTag++;
+ state.seen.add(tag);
+ return new Set(type.params.flatMap(p => {
+ const innerType = p(tag);
+ if (state.seen.has(innerType)) {
+ return []; // no endless recursion!
+ }
+ return [...__occurring(state)(typeVars)(innerType)];
+ }));
+};
+
+// From the given set of type variables, return only those that occur in the given type.
+export const occurring = (type, typeVars) => {
+ return __occurring({nextTag:0, seen: new Set()})(typeVars)(type);
};
// Merge 2 substitution-mappings, uni-directional.
@@ -64,16 +73,16 @@ export const mergeTwoWay = (m1, m2) => {
// checkConflict(m2, m1); // <- don't think this is necessary...
// actually merge
let stable = false;
- let deleted = new Set();
+ let deletions = new Set();
while (!stable) {
let d;
// notice we swap m2 and m1, so the rewriting can happen both ways:
[stable, m2, m1, d] = mergeOneWay(m1, m2);
- deleted = deleted.union(d);
+ deletions = deletions.union(d);
}
const result = {
substitutions: new Map([...m1, ...m2]),
- deleted, // deleted type variables
+ deletions, // deleted type variables
};
// console.log("mergeTwoWay result =", result);
return result;
@@ -81,9 +90,13 @@ export const mergeTwoWay = (m1, m2) => {
// Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog (hence the function name):
// https://www.dai.ed.ac.uk/groups/ssp/bookpages/quickprolog/node12.html
-const unifyInternal = (typeVars, fType, aType) => {
- // console.log("unify", pretty({typeVars, fType, aType}));
-
+//
+// Parameters:
+// typeVars: all the type variables in both fType and aType
+// fType, aType: generic types to unify
+// fStack, aStack: internal use.
+const __unify = (typeVars, fType, aType, fStack=[], aStack=[]) => {
+ console.log("__unify", {typeVars, fType, aType, fStack, aStack});
if (typeVars.has(fType)) {
// simplest case: formalType is a type paramater
// => substitute with actualType
@@ -107,60 +120,84 @@ const unifyInternal = (typeVars, fType, aType) => {
},
};
}
+
// recursively unify
if (fType.symbol !== aType.symbol) {
throw new Error(`cannot unify ${prettyT(fType)} and ${prettyT(aType)}`);
}
- else {
- // console.log("symbols match - unify recursively", formal.symbol);
- const unifiedParams =
- zip(fType.params, aType.params)
- .map(([fParam, aParam]) => unifyInternal(typeVars, fParam, aParam));
- const {substitutions, deleted} =
- unifiedParams.reduce(({substitutions: s, deleted: d}, cur) => {
- // console.log('merging', s, cur.substitutions);
- const {substitutions, deleted} = mergeTwoWay(s, cur.substitutions);
- return {
- substitutions,
- deleted: deleted.union(d),
- };
- }, { substitutions: new Map(), deleted: new Set() });
- // console.log(pretty({unifiedParams}));
- return {
- substitutions,
- genericType: {
- typeVars: typeVars.difference(substitutions).difference(deleted),
- type: {
- symbol: fType.symbol,
- params: unifiedParams.map(p => p.genericType.type),
- },
- },
- };
- }
+
+ const fTag = fStack.length;
+ const aTag = aStack.length;
+
+ const unifications =
+ zip(fType.params, aType.params)
+ .map(([getFParam, getAParam]) => {
+ const fParam = getFParam(fTag);
+ const aParam = getAParam(aTag);
+ // type recursively points to an enclosing type that we've already seen
+ if (fStack[fParam] !== aStack[aParam]) {
+ // note that both are also allowed not to be mapped (undefined)
+ throw new Error("cannot unify: types differ in their recursion");
+ }
+ if (fStack[fParam] !== undefined) {
+ const type = fStack[fParam];
+ return () => ({
+ substitutions: new Map(),
+ genericType: {
+ typeVars,
+ type,
+ },
+ });
+ }
+ return parent => __unify(typeVars, fParam, aParam,
+ [...fStack, parent],
+ [...aStack, parent]);
+ });
+
+ const unifiedParams = unifications.map(getParam => {
+ return parent => getParam(parent).genericType.type;
+ });
+ const type = {
+ symbol: fType.symbol,
+ params: unifiedParams,
+ };
+
+ const [unifiedSubstitutions, unifiedTypeVars] = unifications.reduce((acc, getParam) => {
+ const self = Symbol();
+ const {substitutions, deletions} = mergeTwoWay(acc[0], getParam(self).substitutions);
+ return [substitutions, acc[1]
+ .difference(substitutions)
+ .difference(deletions)];
+ }, [new Map(), typeVars]);
+
+ return {
+ substitutions: unifiedSubstitutions,
+ genericType: {
+ typeVars: unifiedTypeVars,
+ type,
+ },
+ };
};
export const unify = (fGenericType, aGenericType) => {
let allTypeVars;
[allTypeVars, fGenericType, aGenericType] = safeUnionTypeVars(fGenericType, aGenericType);
- const {genericType} = unifyInternal(
- allTypeVars,
- fGenericType.type,
- aGenericType.type,
- )
+ const {genericType} = __unify(allTypeVars, fGenericType.type, aGenericType.type);
return recomputeTypeVars(genericType);
-}
+};
-export const substitute = (type, substitutions) => {
- // console.log("substitute", {type, substitutions})
- if (substitutions.has(type)) {
- return substitutions.get(type);
- }
- if (typeof type === "symbol") {
- return type; // nothing to substitute here
- }
- return {
+export const substitute = (type, substitutions, stack=[]) => {
+ console.log('substitute...', {type, substitutions, stack});
+ return substitutions.get(type)
+ || {
symbol: type.symbol,
- params: type.params.map(p => substitute(p, substitutions)),
+ params: type.params.map(getParam => parent => {
+ const param = getParam(stack.length);
+ const have = stack[param];
+ return (have !== undefined)
+ ? have
+ : substitute(param, substitutions, [...stack, parent]);
+ }),
};
};
@@ -178,10 +215,9 @@ export const assignFn = (genFnType, paramType) => {
[allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType);
const [inType] = genFnType.type.params;
const {substitutions} = unifyInternal(allTypeVars, inType, paramType.type);
- // console.log({genFnType: prettyT(genFnType), paramType: prettyT(paramType), substitutions})
const substitutedFnType = substitute(genFnType.type, substitutions);
return recomputeTypeVars(onlyOccurring(substitutedFnType, allTypeVars));
-}
+};
export const recomputeTypeVars = (genType) => {
const newTypeVars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'].map(Symbol);
@@ -195,11 +231,11 @@ export const recomputeTypeVars = (genType) => {
type: substitute(genType.type, subst),
};
return substType;
-}
+};
export const safeUnionTypeVars = (genTypeA, genTypeB) => {
const substTypeA = recomputeTypeVars(genTypeA);
const substTypeB = recomputeTypeVars(genTypeB);
const allTypeVars = substTypeA.typeVars.union(substTypeB.typeVars);
return [allTypeVars, substTypeA, substTypeB];
-}
\ No newline at end of file
+};
diff --git a/lib/point.js b/lib/point.js
index 89f8925..cb116cb 100644
--- a/lib/point.js
+++ b/lib/point.js
@@ -41,9 +41,9 @@ export const ModulePoint = {l:[
{i: PointCartesian2D , t: Type},
{i: PointPolar2D , t: Type},
- ...typedFnType(cart2polar, fnType => fnType(PointCartesian2D)(PointPolar2D)),
+ ...typedFnType(cart2polar, fnType => fnType(() => PointCartesian2D)(() => PointPolar2D)),
- ...typedFnType(polar2cart, fnType => fnType(PointPolar2D)(PointCartesian2D)),
+ ...typedFnType(polar2cart, fnType => fnType(() => PointPolar2D)(() => PointCartesian2D)),
// Double -> Double -> PointCartesian2D -> PointCartesian2D
...typedFnType(translate, fnType =>
diff --git a/lib/point_structural.js b/lib/point_structural.js
index cf866a0..d530c82 100644
--- a/lib/point_structural.js
+++ b/lib/point_structural.js
@@ -103,10 +103,10 @@ export const ModulePoint = {l:[
{i: getR, t: getRFnType},
{i: getΘ, t: getΘFnType},
- ...typedFnType(cart2polar, fnType => fnType(NPoint2DCartesian)(NPoint2DPolar)),
- ...typedFnType(polar2cart, fnType => fnType(NPoint2DPolar)(NPoint2DCartesian)),
+ ...typedFnType(cart2polar, fnType => fnType(() => NPoint2DCartesian)(() => NPoint2DPolar)),
+ ...typedFnType(polar2cart, fnType => fnType(() => NPoint2DPolar)(() => NPoint2DCartesian)),
- // ...typedFnType(polar2cart, fnType => fnType(Point2DPolar)(Point2DCartesian)),
+ // ...typedFnType(polar2cart, fnType => fnType(() => Point2DPolar)(() => Point2DCartesian)),
// // Double -> Double -> PointCartesian2D -> PointCartesian2D
// ...typedFnType(translate, fnType =>
@@ -118,9 +118,9 @@ export const ModulePoint = {l:[
// (Point2DCartesian)
// (Point2DCartesian)))),
- // ...typedFnType(cart2tuple, fnType => fnType(Point2DCartesian)(prodType(Double)(Double))),
+ // ...typedFnType(cart2tuple, fnType => fnType(() => Point2DCartesian)(prodType(() => Double)(() => () => Double))),
- // ...typedFnType(polar2tuple, fnType => fnType(Point2DPolar)(prodType(Double)(Double))),
+ // ...typedFnType(polar2tuple, fnType => fnType(() => Point2DPolar)(prodType(() => Double)(() => () => Double))),
// // Double -> PointPolar2D -> PointPolar2D
// ...typedFnType(rotate, fnType =>
diff --git a/lib/symbol.js b/lib/symbol.js
index 1bb94e8..939dc93 100644
--- a/lib/symbol.js
+++ b/lib/symbol.js
@@ -17,5 +17,5 @@ export const ModuleSymbol = {l:[
(String)
),
- ...typedFnType(eqSymbol, fnType => fnType(SymbolT)(fnType(SymbolT)(Bool))),
+ ...typedFnType(eqSymbol, fnType => fnType(() => SymbolT)(fnType(SymbolT)(() => Bool))),
]};
diff --git a/primitives/bool.js b/primitives/bool.js
index 40d265a..1a1495c 100644
--- a/primitives/bool.js
+++ b/primitives/bool.js
@@ -11,5 +11,5 @@ export const ModuleBool = {l:[
{i: Bool , t: Type},
// Bool -> Bool -> Bool
- ...typedFnType(eqBool, fnType => fnType(Bool)(fnType(Bool)(Bool))),
+ ...typedFnType(eqBool, fnType => fnType(() => Bool)(fnType(Bool)(() => Bool))),
]};
diff --git a/primitives/byte.js b/primitives/byte.js
index 563e554..0ff79bc 100644
--- a/primitives/byte.js
+++ b/primitives/byte.js
@@ -7,5 +7,5 @@ const eqByte = x => y => x === y;
export const ModuleByte = {l:[
{i: Byte , t: Type },
- ...typedFnType(eqByte, fnType => fnType(Byte)(fnType(Byte)(Bool))),
+ ...typedFnType(eqByte, fnType => fnType(() => Byte)(fnType(Byte)(() => Bool))),
]};
diff --git a/primitives/double.js b/primitives/double.js
index 55ff842..7de914c 100644
--- a/primitives/double.js
+++ b/primitives/double.js
@@ -9,7 +9,7 @@ export const eqDouble = x => y => x === y;
export const ModuleDouble = {l:[
{i: Double, t: Type},
- ...typedFnType(addDouble, fnType => fnType(Double)(fnType(Double)(Double))),
- ...typedFnType(mulDouble, fnType => fnType(Double)(fnType(Double)(Double))),
- ...typedFnType(eqDouble, fnType => fnType(Double)(fnType(Double)(Bool))),
+ ...typedFnType(addDouble, fnType => fnType(() => Double)(fnType(Double)(() => Double))),
+ ...typedFnType(mulDouble, fnType => fnType(() => Double)(fnType(Double)(() => Double))),
+ ...typedFnType(eqDouble, fnType => fnType(() => Double)(fnType(Double)(() => Bool))),
]};
diff --git a/primitives/dynamic.js b/primitives/dynamic.js
index 769470b..bd6584f 100644
--- a/primitives/dynamic.js
+++ b/primitives/dynamic.js
@@ -1,5 +1,5 @@
import { typedFnType } from "../structures/types.js";
-import { Any, Type } from "./types.js";
+import { Top, Type } from "./types.js";
import { makeTypeConstructor } from "../type_constructor.js";
// A type-link, connecting a value to its Type.
@@ -11,9 +11,9 @@ export const getType = lnk => lnk.t;
export const ModuleDynamic = {l:[
{i: Dynamic, t: Type},
- {i: Any , t: Type},
+ {i: Top , t: Type},
- ...typedFnType(getInst, fnType => fnType(Dynamic)(Any)),
- ...typedFnType(getType, fnType => fnType(Dynamic)(Any)),
+ ...typedFnType(getInst, fnType => fnType(() => Dynamic)(() => Top)),
+ ...typedFnType(getType, fnType => fnType(() => Dynamic)(() => Top)),
]};
diff --git a/primitives/generic_type.js b/primitives/generic_type.js
index 111505f..580c34b 100644
--- a/primitives/generic_type.js
+++ b/primitives/generic_type.js
@@ -1,6 +1,6 @@
import { newLeft, newRight } from "../structures/sum.js";
import { setType, sumType, typedFnType } from "../structures/types.js";
-import { Any, GenericType, SymbolT, Type, Unit } from "./types.js";
+import { Top, 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)
: newLeft(unit);
export const ModuleGenericType = {l:[
- {i: GenericType, t: Any},
+ {i: GenericType, t: Top},
- // ...typedFnType(getType, fnType => fnType(GenericType)(Type)),
+ // ...typedFnType(getType, fnType => fnType(() => GenericType)(() => Type)),
- // ...typedFnType(getTypeVars, fnType => fnType(GenericType)(setType(SymbolT))),
+ // ...typedFnType(getTypeVars, fnType => fnType(() => GenericType)(() => set(() => SymbolT))),
- ...typedFnType(toNonGeneric, fnType => fnType(GenericType)(sumType(Unit)(Type))),
+ ...typedFnType(toNonGeneric, fnType => fnType(() => GenericType)(sumType(() => Unit)(() => () => Type))),
]};
diff --git a/primitives/int.js b/primitives/int.js
index c956e87..d4eb833 100644
--- a/primitives/int.js
+++ b/primitives/int.js
@@ -13,7 +13,7 @@ const deserialize = str => BigInt(str);
export const ModuleInt = {l:[
{i: Int , t: Type },
- ...typedFnType(addInt, fnType => fnType(Int)(fnType(Int)(Int))),
- ...typedFnType(mulInt, fnType => fnType(Int)(fnType(Int)(Int))),
- ...typedFnType(eqInt , fnType => fnType(Int)(fnType(Int)(Bool))),
+ ...typedFnType(addInt, fnType => fnType(() => Int)(fnType(Int)(() => Int))),
+ ...typedFnType(mulInt, fnType => fnType(() => Int)(fnType(Int)(() => Int))),
+ ...typedFnType(eqInt , fnType => fnType(() => Int)(fnType(Int)(() => Bool))),
]};
diff --git a/primitives/type.js b/primitives/type.js
index a1f994a..a8dbd11 100644
--- a/primitives/type.js
+++ b/primitives/type.js
@@ -26,8 +26,8 @@ export const ModuleType = {l:[
(Bool)
)),
- ...typedFnType(getSymbol, fnType => fnType(Type)(SymbolT)),
- ...typedFnType(getParams, fnType => fnType(Type)(lsType(Type))),
+ ...typedFnType(getSymbol, fnType => fnType(() => Type)(() => SymbolT)),
+ ...typedFnType(getParams, fnType => fnType(() => Type)(() => lsType(() =>Type))),
- ...typedFnType(isFunction, fnType => fnType(Type)(Bool)),
+ ...typedFnType(isFunction, fnType => fnType(() => Type)(() => Bool)),
]};
\ No newline at end of file
diff --git a/primitives/types.js b/primitives/types.js
index 0a5b5c0..f8e05f4 100644
--- a/primitives/types.js
+++ b/primitives/types.js
@@ -2,16 +2,16 @@
import { makeTypeConstructor } from "../type_constructor.js";
-export const SymbolInt = Symbol('Int');
-export const SymbolBool = Symbol('Bool');
-export const SymbolDouble = Symbol('Double');
-export const SymbolByte = Symbol('Byte');
-export const SymbolChar = Symbol('Char');
-export const SymbolUnit = Symbol('Unit');
-export const SymbolBottom = Symbol('⊥');
-export const SymbolSymbol = Symbol('Symbol');
-export const SymbolType = Symbol('Type');
-export const symbolAny = Symbol('Any');
+export const SymbolInt = Symbol('Int');
+export const SymbolBool = Symbol('Bool');
+export const SymbolDouble = Symbol('Double');
+export const SymbolByte = Symbol('Byte');
+export const SymbolChar = Symbol('Char');
+export const SymbolUnit = Symbol('Unit');
+export const SymbolBottom = Symbol('⊥');
+export const SymbolSymbol = Symbol('Symbol');
+export const SymbolType = Symbol('Type');
+export const symbolTop = Symbol('⊤');
export const SymbolGenericType = Symbol('GenericType');
export const Int = makeTypeConstructor(SymbolInt)(0);
@@ -28,23 +28,24 @@ export const Bottom = makeTypeConstructor(SymbolBottom)(0);
export const SymbolT = makeTypeConstructor(SymbolSymbol)(0);
-// Types are typed by Any
+// Types are typed by Top
export const Type = makeTypeConstructor(SymbolType)(0);
export const GenericType = makeTypeConstructor(SymbolGenericType)(0);
-// Everything is typed by Any
-export const Any = makeTypeConstructor(symbolAny)(0);
+// Everything is typed by Top
+export const Top = makeTypeConstructor(symbolTop)(0);
export const ModuleSymbols = {l:[
- {i: SymbolInt , t: SymbolT},
- {i: SymbolBool , t: SymbolT},
- {i: SymbolDouble, t: SymbolT},
- {i: SymbolByte , t: SymbolT},
- {i: SymbolChar , t: SymbolT},
- {i: SymbolUnit , t: SymbolT},
- {i: SymbolBottom, t: SymbolT},
- {i: SymbolSymbol, t: SymbolT},
- {i: SymbolType , t: SymbolT},
+ {i: SymbolInt , t: SymbolT},
+ {i: SymbolBool , t: SymbolT},
+ {i: SymbolDouble , t: SymbolT},
+ {i: SymbolByte , t: SymbolT},
+ {i: SymbolChar , t: SymbolT},
+ {i: SymbolUnit , t: SymbolT},
+ {i: SymbolBottom , t: SymbolT},
+ {i: SymbolSymbol , t: SymbolT},
+ {i: SymbolType , t: SymbolT},
{i: SymbolGenericType, t: SymbolT},
+ {i: symbolTop , t: SymbolT},
]};
diff --git a/primitives/unit.js b/primitives/unit.js
index af5156b..99f1b30 100644
--- a/primitives/unit.js
+++ b/primitives/unit.js
@@ -11,5 +11,5 @@ export const ModuleUnit = {l:[
{i: Unit, t: Type},
// Unit -> Unit -> Bool
- ...typedFnType(eqUnit, fnType => fnType(Unit)(fnType(Unit)(Bool))),
+ ...typedFnType(eqUnit, fnType => fnType(() => Unit)(fnType(Unit)(() => Bool))),
]};
diff --git a/structures/enum.js b/structures/enum.js
index 34d74c6..40f1ca7 100644
--- a/structures/enum.js
+++ b/structures/enum.js
@@ -17,7 +17,7 @@ export const enumType = variants => {
}
const [variant, ...rest] = variants;
const variantType = getRight(variant);
- return sumType(variantType)(enumType(rest));
+ return sumType(() => variantType)(() => enumType(rest));
};
const eatParameters = (numParams, result) => {
diff --git a/structures/list.js b/structures/list.js
index 2d409db..406edba 100644
--- a/structures/list.js
+++ b/structures/list.js
@@ -7,7 +7,7 @@ import { Dynamic } from "../primitives/dynamic.js"
// 'normal' implementation
export const emptyList = {l:[]};
-const emptyListType = makeGeneric(a => lsType(a));
+// const emptyListType = makeGeneric(a => lsType(() => a));
export const get = ls => i => ls.l[i];
export const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)});
export const push = ls => elem => ({l:ls.l.concat([elem])});
@@ -21,62 +21,62 @@ export const fold = ls => callback => initial => {
return acc;
}
-export const String = lsType(Char); // alias
-export const Module = lsType(Dynamic);
+export const String = lsType(() =>Char); // alias
+export const Module = lsType(() =>Dynamic);
export const ModuleList = {l:[
- // Type -> Type
- ...typedFnType(lsType, fnType =>
- fnType
- /* in */ (Type)
- /* out */ (Type)
- ),
+ // // Type -> Type
+ // ...typedFnType(lsType, fnType =>
+ // fnType
+ // /* in */ (Type)
+ // /* out */ (Type)
+ // ),
- // [a]
- {i: emptyList, t: emptyListType},
- {i: emptyListType, t: GenericType},
+ // // [a]
+ // // {i: emptyList, t: emptyListType},
+ // // {i: emptyListType, t: GenericType},
- // [a] -> Int -> a
- ...typedFnType(get, fnType =>
- makeGeneric(a =>
- fnType
- /* in */ (lsType(a))
- /* out */ (fnType
- /* in */ (Int)
- /* out */ (a)
- )), GenericType),
+ // // [a] -> Int -> a
+ // ...typedFnType(get, fnType =>
+ // makeGeneric(a =>
+ // fnType
+ // /* in */ (() => lsType(() => a))
+ // /* out */ (() => fnType
+ // /* in */ (() => Int)
+ // /* out */ (() => a)
+ // )), GenericType),
- // [a] -> Int -> a -> [a]
- ...typedFnType(put, fnType =>
- makeGeneric(a =>
- fnType
- /* in */ (lsType(a))
- /* out */ (fnType
- /* in */ (Int)
- /* out */ (fnType
- /* in */ (a)
- /* out */ (lsType(a))
- )
- )), GenericType),
+ // // [a] -> Int -> a -> [a]
+ // ...typedFnType(put, fnType =>
+ // makeGeneric(a =>
+ // fnType
+ // /* in */ (() => lsType(() => a))
+ // /* out */ (() => fnType
+ // /* in */ (() => Int)
+ // /* out */ (() => fnType
+ // /* in */ (() => a)
+ // /* out */ (() => lsType(() => a))
+ // )
+ // )), GenericType),
- // [a] -> a -> [a]
- ...typedFnType(push, fnType =>
- makeGeneric(a =>
- fnType
- (lsType(a))
- (fnType
- (a)
- (lsType(a))
- )
- ), GenericType),
+ // // [a] -> a -> [a]
+ // ...typedFnType(push, fnType =>
+ // makeGeneric(a =>
+ // fnType
+ // (() => lsType(() => a))
+ // (() => fnType
+ // (() => a)
+ // (() => lsType(() => a))
+ // )
+ // ), GenericType),
- // [a] -> (a -> b) -> [b]
- ...typedFnType(map, fnType =>
- makeGeneric((a, b) =>
- fnType
- (lsType(a))
- (fnType
- (fnType(a)(b))
- (lsType(b))
- )), 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/product.js b/structures/product.js
index b66cb9e..b0ad9dd 100644
--- a/structures/product.js
+++ b/structures/product.js
@@ -32,7 +32,7 @@ export const ModuleProduct = {l: [
(a)
(fnType
(b)
- (prodType(a)(b))
+ (prodType(() => a)(() => b))
)
), GenericType),
@@ -40,7 +40,7 @@ export const ModuleProduct = {l: [
...typedFnType(getLeft, fnType =>
makeGeneric((a, b) =>
fnType
- (prodType(a)(b))
+ (prodType(() => a)(() => b))
(a)
), GenericType),
@@ -48,7 +48,7 @@ export const ModuleProduct = {l: [
...typedFnType(getRight, fnType =>
makeGeneric((a, b) =>
fnType
- (prodType(a)(b))
+ (prodType(() => a)(() => b))
(b)
), GenericType),
]};
diff --git a/structures/set.js b/structures/set.js
index df02e6c..69f6655 100644
--- a/structures/set.js
+++ b/structures/set.js
@@ -19,7 +19,7 @@ export class RBTreeWrapper {
// (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)));
+// const emptySetType = makeGeneric(a => fnType(() => fnType(a)(fnType(a)(Int)))(() => set(() => 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));
@@ -57,7 +57,7 @@ export const ModuleSet = {l:[
// ...typedFnType(has, fnType =>
// makeGeneric(a =>
// fnType
- // /* in */ (setType(a))
+ // /* in */ (set(() => a))
// /* out */ (fnType
// /* in */ (a)
// /* out */ (Bool)
@@ -66,9 +66,9 @@ export const ModuleSet = {l:[
// ...typedFnType(add, fnType =>
// makeGeneric(a =>
// fnType
- // /* in */ (setType(a))
+ // /* in */ (set(() => a))
// /* out */ (fnType
// /* in */ (a)
- // /* out */ (setType(a))
+ // /* out */ (set(() => a))
// )), GenericType),
]};
diff --git a/structures/struct.js b/structures/struct.js
index fa43919..c2edd7f 100644
--- a/structures/struct.js
+++ b/structures/struct.js
@@ -14,7 +14,7 @@ export const structType = fields => {
}
const [field, ...rest] = fields;
const fieldType = getRight(field);
- return prodType(fieldType)(structType(rest));
+ return prodType(() => fieldType)(() => structType(rest));
};
export const makeConstructor = fields => {
@@ -41,7 +41,7 @@ export const makeConstructorType = type => fields => {
}
const [field, ...rest] = fields;
const fieldType = getRight(field);
- return fnType(fieldType)(makeConstructorType(rest));
+ return fnType(() => fieldType)(() => makeConstructorType(rest));
};
export const makeGetters = fields => {
@@ -60,6 +60,6 @@ export const makeGetters = fields => {
export const makeGettersTypes = type => fields => {
return fields.map(field => {
const fieldType = getRight(field);
- return fnType(type)(fieldType);
+ return fnType(() => type)(() => fieldType);
});
};
diff --git a/structures/sum.js b/structures/sum.js
index 197f38d..f360af1 100644
--- a/structures/sum.js
+++ b/structures/sum.js
@@ -23,40 +23,40 @@ export const ModuleSum = {l:[
// Type -> Type -> Type
...typedFnType(sumType, fnType =>
fnType
- (Type)
- (fnType
- (Type)
- (Type)
+ (() => Type)
+ (() => fnType
+ (() => Type)
+ (() => Type)
),
),
- // a -> a | b
- ...typedFnType(newLeft, fnType =>
- makeGeneric((a, b) =>
- fnType
- (a)
- (sumType(a)(b))
- ), GenericType),
+ // // a -> a | b
+ // ...typedFnType(newLeft, fnType =>
+ // makeGeneric((a, b) =>
+ // fnType
+ // (a)
+ // (sumType(() => a)(() => b))
+ // ), GenericType),
- // b -> a | b
- ...typedFnType(newRight, fnType =>
- makeGeneric((a, b) =>
- fnType
- (b)
- (sumType(a)(b))
- ), GenericType),
+ // // b -> a | b
+ // ...typedFnType(newRight, fnType =>
+ // makeGeneric((a, b) =>
+ // fnType
+ // (b)
+ // (sumType(() => a)(() => b))
+ // ), GenericType),
- // a | b -> (a -> c, b-> c) -> c
- ...typedFnType(match, fnType =>
- makeGeneric((a, b, c) =>
- fnType
- (sumType(a)(b))
- (fnType
- (prodType
- (fnType(a)(c))
- (fnType(b)(c))
- )
- (c)
- )
- ), GenericType),
+ // // a | b -> (a -> c, b-> c) -> c
+ // ...typedFnType(match, fnType =>
+ // makeGeneric((a, b, c) =>
+ // fnType
+ // (() => sumType(() => a)(() => b))
+ // (() => fnType
+ // (() => prodType
+ // (() => fnType(() => a)(() => c))
+ // (() => fnType(() => b)(() => c))
+ // )
+ // (() => c)
+ // )
+ // ), GenericType),
]};
diff --git a/structures/types.js b/structures/types.js
index 7526a33..f7f07ed 100644
--- a/structures/types.js
+++ b/structures/types.js
@@ -17,7 +17,7 @@ export const isFunction = type => getSymbol(type) === symbolFunction;
export const typedFnType = (instance, callback, typeOfType = Type) => {
const fnTs = [];
const wrappedFnType = inType => outType => {
- const fnT = fnType(inType)(outType);
+ const fnT = fnType(() => inType)(() => outType);
fnTs.push(fnT);
return fnT;
};
diff --git a/type_constructor.js b/type_constructor.js
index 93bf1ed..3c5defc 100644
--- a/type_constructor.js
+++ b/type_constructor.js
@@ -1,38 +1,55 @@
-import { DefaultMap } from "./util/defaultmap.js";
+// import { DefaultMap } from "./util/defaultmap.js";
-const nullaryTypeConstructors = new DefaultMap(
- // symbol -> 0-ary type constructor (= a type, basically)
- symbol => ({
- symbol,
- params: [],
- }));
+// const nullaryTypeConstructors = new DefaultMap(
+// // symbol -> 0-ary type constructor (= a type, basically)
+// symbol => ({
+// symbol,
+// params: [],
+// }));
-const makeTypeConstructorInternal = (symbol, n_ary, params = []) => {
- // console.log("n_ary:", n_ary);
- if (n_ary === 0 || n_ary === 0n) {
- // a bit dirty, but otherwise OK
- if (params.length > 0) {
- const result = { symbol, params };
- // console.log("result:", result);
- return result;
- }
- else {
- const result = nullaryTypeConstructors.getdefault(symbol, true)
- // console.log("result:", result);
- return result;
- }
+
+// // nAry: how many more type parameters to take
+// // params: the type params we already took
+// const makeTypeConstructorInternal = (symbol, nAry, params = []) => {
+// // console.log("n_ary:", n_ary);
+// if (nAry === 0 || nAry === 0n) {
+// // a bit dirty, but otherwise OK
+// if (params.length > 0) {
+// const result = { symbol, params };
+// // console.log("result:", result);
+// return result;
+// }
+// else {
+// const result = nullaryTypeConstructors.getdefault(symbol, true)
+// // console.log("result:", result);
+// return result;
+// }
+// }
+// else {
+// // use DefaultMap, so we only construct every type once (saves memory)
+// const m = new DefaultMap(typeParam => makeTypeConstructorInternal(symbol, nAry - 1, params.concat([typeParam])));
+// const fnName = 'make'+symbol.description+'Type';
+// return {
+// [fnName]: typeParam => m.getdefault(typeParam, true),
+// }[fnName];
+// }
+// };
+
+const __makeTypeConstructor = (symbol, nAry, params) => {
+ if (nAry === 0) {
+ return { symbol, params };
}
- else {
- const m = new DefaultMap(typeParam => makeTypeConstructorInternal(symbol, n_ary - 1, params.concat([typeParam])));
- const fnName = 'make'+symbol.description+'Type';
- return {
- [fnName]: typeParam => m.getdefault(typeParam, true),
- }[fnName];
+ return typeParam => {
+ if (typeof typeParam !== 'function') {
+ throw new Error("all type params must be functions");
+ }
+ return __makeTypeConstructor(symbol, nAry-1, params.concat([typeParam]));
}
-};
+}
// Creates a new nominal type
-export const makeTypeConstructor = symbol => nAry => makeTypeConstructorInternal(symbol, nAry);
+// export const makeTypeConstructor = symbol => nAry => makeTypeConstructorInternal(symbol, nAry);
+export const makeTypeConstructor = symbol => nAry => __makeTypeConstructor(symbol, nAry, []);
export const getSymbol = type => type.symbol;
export const getParams = type => ({ l: type.params });
diff --git a/typeclasses/eq.js b/typeclasses/eq.js
index 37b571b..05b7b4f 100644
--- a/typeclasses/eq.js
+++ b/typeclasses/eq.js
@@ -9,7 +9,7 @@ export const getEq = numDict => numDict.eq;
export const ModuleEq = {l:[
// type constructor: Type -> Type
- ...typedFnType(eqDictType, fnType => fnType(Type)(Type)),
+ ...typedFnType(eqDictType, fnType => fnType(() => Type)(() => Type)),
// (EqDict a) -> a -> a -> Bool
...typedFnType(getEq, fnType =>
diff --git a/typeclasses/num.js b/typeclasses/num.js
index eaf288c..e78f10c 100644
--- a/typeclasses/num.js
+++ b/typeclasses/num.js
@@ -17,7 +17,7 @@ const [getAddMulFnType, typesOfFns] = typedFnType2(fnType =>
(numDictType(a))
(fnType
(a)
- (fnType(a)(a))
+ (fnType(() => a)(() => a))
)));
export const ModuleNum = {l:[
diff --git a/util/pretty.js b/util/pretty.js
index fbf0c93..1138d45 100644
--- a/util/pretty.js
+++ b/util/pretty.js
@@ -1,38 +1,43 @@
import { inspect } from 'node:util';
-import { symbolFunction, symbolList, symbolProduct, symbolSum } from '../structures/types.js';
+import { symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from '../structures/types.js';
+import { mapRecursiveStructure } from './util.js';
export function pretty(obj) {
return inspect(obj, { colors: true, depth: null, breakLength: 120 });
}
// Pretty print type
-export function prettyT(type) {
- // console.log("pretty:", type);
- if (typeof type === "symbol") {
- return type.description;
- }
- if (type.typeVars) {
- if (type.typeVars.size > 0) {
- return `∀${[...type.typeVars].map(prettyT).sort((a, b) => a.localeCompare(b)).join(",")}: ${prettyT(type.type)}`;
+export const prettyT = type => {
+ return mapRecursiveStructure(([type, m, seen], map) => {
+ if (typeof type === "symbol") {
+ return type.description;
+ }
+ if (!m.has(type)) {
+ m.add(type); // next time we encounter this type, we'll only render a placeholder
+ const params = type.params.map(p => map([p(type), m, seen])());
+ // if while visiting the children, we encountered ourselves, add annotation:
+ const annot = seen.has(type) ? seen.get(type) : ``;
+ return renderType(type.symbol, annot, params);
}
else {
- return prettyT(type.type);
+ if (!seen.has(type)) {
+ seen.set(type, `#${seen.size}`);
+ }
+ return seen.get(type);
}
- }
- if (type.symbol === symbolFunction) {
- 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])})`;
- }
- if (type.symbol === symbolSum) {
- return `(${prettyT(type.params[0])} | ${prettyT(type.params[1])})`;
- }
- if (type.params.length === 0) {
- return type.symbol.description;
- }
- return `${type.symbol.description}(${type.params.map(prettyT).join(", ")})`;
-}
+ })([type, new Set(), new Map()])();
+};
+
+const renderType = (symbol, annot, params) => {
+ return {
+ [symbolList] : `${annot}[${params[0]}]`,
+ [symbolSet] : `${annot}{${params[0]}}`,
+ [symbolFunction]: `${annot}(${params[0]} -> ${params[1]})`,
+ [symbolSum] : `${annot}(${params[0]} + ${params[1]})`,
+ [symbolProduct] : `${annot}(${params[0]} ⨯ ${params[1]})`,
+ }[symbol] || symbol.description;
+};
+
+export const prettyGenT = genericType => {
+ return `∀${[...genericType.typeVars].map(prettyT).sort((a, b) => a.localeCompare(b)).join(",")}: ${prettyT(genericType.type)}`;
+};
diff --git a/util/util.js b/util/util.js
index d1ab25f..66cbde6 100644
--- a/util/util.js
+++ b/util/util.js
@@ -1,3 +1,5 @@
+import { lsType, setType } from "../structures/types.js";
+import { pretty } from "./pretty.js";
// re-inventing the wheel:
export function deepEqual(a, b) {
@@ -46,4 +48,54 @@ export function capitalizeFirstLetter(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
+const _mapRecursiveStructure = mapping => transform => root => {
+ const found = mapping.get(root);
+ if (found) {
+ // already mapped
+ // return existing result to prevent endless recursion
+ return found;
+ }
+ // note the indirection (wrapped in lamda), this allows the user to recursively map the children (which may refer to the root) without yet having finished mapping the root.
+ let memo;
+ const result = () => {
+ // memoization is necessary for correctness
+ return memo || (memo = transform(root, _mapRecursiveStructure(mapping)(transform)));
+ };
+ mapping.set(root, result);
+ return result;
+};
+export const mapRecursiveStructure = _mapRecursiveStructure(new Map());
+
+const _transformType = mapping => transform => type => {
+ const found = mapping.get(type);
+ if (found) {
+ return found;
+ }
+ const mapped = transform(type, _transformType(mapping)(transform));
+ mapping.set(type, mapped);
+ return mapped;
+}
+
+export const transformType = _transformType(new Map());
+
+const __memo = () => {
+ let memo;
+ return fn => memo || (memo = fn());
+}
+export const memo = fn => {
+ return __memo()(fn);
+}
+
+// let infiniteSet = mapRecursiveStructure((type, map) => {
+// const ps = [];
+// for (const p of type.params) {
+// ps.push(map(p()));
+// }
+// return setType(ps[0]);
+// })(infiniteList)();
+// console.log(infiniteSet);
+// // while (true) {
+// // console.log(infiniteSet);
+// // infiniteSet = infiniteSet.params[0]();
+// // }
diff --git a/versioning/value.js b/versioning/value.js
index 2184615..7e5cc4a 100644
--- a/versioning/value.js
+++ b/versioning/value.js
@@ -1,11 +1,7 @@
+import { fnType } from "../structures/types.js";
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",
@@ -44,7 +40,7 @@ export const transform = input => fn => {
}
};
-// Value -> Set>
+// Value -> Set>
export const getReadDependencies = value => {
if (value.kind === "literal") {
return new Set();