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();