recursive types (and operations on them, like pretty-printing, comparison and unification) seem to be working.
big part of the code base still needs to be 'ported' to the updated type constructors.
This commit is contained in:
parent
55c5d7cffa
commit
8eec5b9239
34 changed files with 523 additions and 295 deletions
|
|
@ -26,4 +26,4 @@ export const compareUnits = x => y => 0;
|
||||||
|
|
||||||
// Note: dirty assumption that every symbol has unique description.
|
// Note: dirty assumption that every symbol has unique description.
|
||||||
// This will be fixed once we move from symbols to real UUIDs.
|
// 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);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,38 @@
|
||||||
import { getParams, getSymbol } from "../type_constructor.js";
|
import { getParams, getSymbol } from "../type_constructor.js";
|
||||||
import { compareSymbols } from "./primitives.js";
|
import { compareBools, compareSymbols } from "./primitives.js";
|
||||||
import { compareLists } from "./structures.js";
|
import { compareLists } from "./structures.js";
|
||||||
|
|
||||||
export const compareTypes = x => y =>
|
const __compareTypes = state => typeX => typeY => {
|
||||||
compareSymbols(getSymbol(x))(getSymbol(y))
|
// tagX and tagY: just something unique & deterministic that can serve as JS map key
|
||||||
|| compareLists(compareTypes)(getParams(x))(getParams(y));
|
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);
|
||||||
|
|
|
||||||
87
examples/compare_types.js
Normal file
87
examples/compare_types.js
Normal file
|
|
@ -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)
|
||||||
|
|
@ -7,7 +7,7 @@ import { lsType, prettyT } from "../structures/types.js";
|
||||||
|
|
||||||
const variants = [
|
const variants = [
|
||||||
newProduct("price")(Int),
|
newProduct("price")(Int),
|
||||||
newProduct("prices")(lsType(Int)),
|
newProduct("prices")(lsType(() =>Int)),
|
||||||
newProduct("not_found")(Unit),
|
newProduct("not_found")(Unit),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,41 @@
|
||||||
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 } from "../structures/types.js";
|
||||||
import { assign, makeGeneric, unify } from "../generics/generics.js";
|
import { assign, makeGeneric, unify } from "../generics/generics.js";
|
||||||
|
import { prettyGenT, prettyT } from "../util/pretty.js";
|
||||||
|
|
||||||
// a -> Int
|
// 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
|
// 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("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
|
// (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
|
// (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("should be: (Bool -> Bool) -> a");
|
||||||
console.log(prettyT(unify(fnType2, fnType3)));
|
console.log(prettyT(unify(fnType2, fnType3)));
|
||||||
|
|
||||||
// (a -> b) -> [a] -> [b]
|
// (a -> b) -> [a] -> [b]
|
||||||
const mapFnType = makeGeneric((a,b) =>
|
const mapFnType = makeGeneric((a,b) =>
|
||||||
fnType
|
fnType
|
||||||
(fnType(a)(b))
|
(fnType(() => a)(() => b))
|
||||||
(fnType(lsType(a))(lsType(b))))
|
(fnType(() => lsType(() =>a))(() => lsType(() =>b))))
|
||||||
// a -> a
|
// a -> a
|
||||||
const idFnType = makeGeneric((_,__,c) =>
|
const idFnType = makeGeneric((_,__,c) =>
|
||||||
fnType(c)(c));
|
fnType(() => c)(() => c));
|
||||||
console.log("should be: [c] -> [c]");
|
console.log("should be: [c] -> [c]");
|
||||||
console.log(prettyT(assign(mapFnType, idFnType)));
|
console.log(prettyT(assign(mapFnType, idFnType)));
|
||||||
|
|
||||||
// (a -> Int) -> [a] -> a
|
// (a -> Int) -> [a] -> a
|
||||||
const weirdFnType = makeGeneric(a =>
|
const weirdFnType = makeGeneric(a =>
|
||||||
fnType
|
fnType
|
||||||
(fnType(a)(Int))
|
(fnType(() => a)(() => Int))
|
||||||
(fnType
|
(fnType
|
||||||
(lsType(a))
|
(lsType(() =>a))
|
||||||
(a)))
|
(a)))
|
||||||
// we call this function with parameter of type (b -> b) ...
|
// we call this function with parameter of type (b -> b) ...
|
||||||
// giving these substitutions:
|
// giving these substitutions:
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ import { newLeft, newRight, match } from "../structures/sum.js";
|
||||||
import { fnType, sumType } from "../structures/types.js";
|
import { fnType, sumType } from "../structures/types.js";
|
||||||
import { pretty } from '../util/pretty.js';
|
import { pretty } from '../util/pretty.js';
|
||||||
|
|
||||||
const IntOrBool = sumType(Int)(Bool);
|
const IntOrBool = sumType(() => Int)(() => Bool);
|
||||||
|
|
||||||
|
|
||||||
// console.log(int5);
|
// console.log(int5);
|
||||||
|
|
||||||
console.log(pretty(unify(
|
console.log(pretty(unify(
|
||||||
makeGeneric(() => IntOrBool),
|
makeGeneric(() => IntOrBool),
|
||||||
makeGeneric(a => sumType(Int)(a)),
|
makeGeneric(a => sumType(() => Int)(() => a)),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
const cipFunction = (x) => {
|
const cipFunction = (x) => {
|
||||||
|
|
@ -38,7 +38,7 @@ const typeAtCallSite = assign(
|
||||||
makeGeneric((a, b) =>
|
makeGeneric((a, b) =>
|
||||||
fnType
|
fnType
|
||||||
(a)
|
(a)
|
||||||
(sumType(a)(b))
|
(sumType(() => a)(() => b))
|
||||||
),
|
),
|
||||||
makeGeneric(() => Int));
|
makeGeneric(() => Int));
|
||||||
console.log(pretty(typeAtCallSite));
|
console.log(pretty(typeAtCallSite));
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const square = numDict => x => getMul(numDict)(x)(x);
|
||||||
const squareFnType = makeGeneric(a =>
|
const squareFnType = makeGeneric(a =>
|
||||||
fnType
|
fnType
|
||||||
(numDictType(a))
|
(numDictType(a))
|
||||||
(fnType(a)(a))
|
(fnType(() => a)(() => a))
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("should be: Int -> Int");
|
console.log("should be: Int -> Int");
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { isFunction } 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 "../primitives/types.js";
|
import { Top } from "../primitives/types.js";
|
||||||
import { assignFn, makeGeneric, onlyOccurring } from '../generics/generics.js';
|
import { assignFn, makeGeneric, onlyOccurring } from '../generics/generics.js';
|
||||||
import { prettyT } from '../util/pretty.js';
|
import { prettyT } from '../util/pretty.js';
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ class Context {
|
||||||
// console.log(strI, '::', strT);
|
// console.log(strI, '::', strT);
|
||||||
|
|
||||||
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(Top);
|
||||||
if (t.typeVars) {
|
if (t.typeVars) {
|
||||||
// console.log("generic:", prettyT(t));
|
// console.log("generic:", prettyT(t));
|
||||||
this.types.getdefault(t, true).add(GenericType);
|
this.types.getdefault(t, true).add(GenericType);
|
||||||
|
|
@ -59,7 +59,7 @@ class Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.instances.getdefault(t, true).add(i);
|
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) => {
|
const addIfFunctionType = (t, originalT, add) => {
|
||||||
if (isFunction(t)) {
|
if (isFunction(t)) {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { eqType } from "../primitives/type.js";
|
import { eqType } from "../primitives/type.js";
|
||||||
import { zip } from "../util/util.js";
|
import { zip } from "../util/util.js";
|
||||||
import { pretty } from '../util/pretty.js';
|
import { pretty, prettyT } from '../util/pretty.js';
|
||||||
import { prettyT } from "../util/pretty.js";
|
|
||||||
|
|
||||||
// constructor for generic types
|
// constructor for generic types
|
||||||
// for instance, the type:
|
// for instance, the type:
|
||||||
// ∀a: a -> a -> Bool
|
// ∀a: a -> a -> Bool
|
||||||
// is created by
|
// is created by
|
||||||
// makeGeneric(a => fnType(a)(fnType(a)(Bool)))
|
// makeGeneric(a => fnType(() => a)(() => fnType(() => a)(() => Bool)))
|
||||||
export const makeGeneric = callback => {
|
export const makeGeneric = callback => {
|
||||||
// type variables to make available:
|
// type variables to make available:
|
||||||
const typeVars = ['a', 'b', 'c', 'd', 'e'].map(Symbol);
|
const typeVars = ['a', 'b', 'c', 'd', 'e'].map(Symbol);
|
||||||
|
|
@ -20,14 +19,24 @@ export const onlyOccurring = (type, typeVars) => ({
|
||||||
type,
|
type,
|
||||||
});
|
});
|
||||||
|
|
||||||
// From the given set of type variables, return only those that occur in the given type.
|
const __occurring = state => typeVars => type => {
|
||||||
export const occurring = (type, typeVars) => {
|
|
||||||
// console.log("occurring", type);
|
|
||||||
if (typeVars.has(type)) {
|
if (typeVars.has(type)) {
|
||||||
// type IS a type variable:
|
|
||||||
return new Set([type]);
|
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.
|
// Merge 2 substitution-mappings, uni-directional.
|
||||||
|
|
@ -64,16 +73,16 @@ export const mergeTwoWay = (m1, m2) => {
|
||||||
// checkConflict(m2, m1); // <- don't think this is necessary...
|
// checkConflict(m2, m1); // <- don't think this is necessary...
|
||||||
// actually merge
|
// actually merge
|
||||||
let stable = false;
|
let stable = false;
|
||||||
let deleted = new Set();
|
let deletions = new Set();
|
||||||
while (!stable) {
|
while (!stable) {
|
||||||
let d;
|
let d;
|
||||||
// notice we swap m2 and m1, so the rewriting can happen both ways:
|
// notice we swap m2 and m1, so the rewriting can happen both ways:
|
||||||
[stable, m2, m1, d] = mergeOneWay(m1, m2);
|
[stable, m2, m1, d] = mergeOneWay(m1, m2);
|
||||||
deleted = deleted.union(d);
|
deletions = deletions.union(d);
|
||||||
}
|
}
|
||||||
const result = {
|
const result = {
|
||||||
substitutions: new Map([...m1, ...m2]),
|
substitutions: new Map([...m1, ...m2]),
|
||||||
deleted, // deleted type variables
|
deletions, // deleted type variables
|
||||||
};
|
};
|
||||||
// console.log("mergeTwoWay result =", result);
|
// console.log("mergeTwoWay result =", result);
|
||||||
return 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):
|
// 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
|
// 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)) {
|
if (typeVars.has(fType)) {
|
||||||
// simplest case: formalType is a type paramater
|
// simplest case: formalType is a type paramater
|
||||||
// => substitute with actualType
|
// => substitute with actualType
|
||||||
|
|
@ -107,60 +120,84 @@ const unifyInternal = (typeVars, fType, aType) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursively unify
|
// recursively unify
|
||||||
if (fType.symbol !== aType.symbol) {
|
if (fType.symbol !== aType.symbol) {
|
||||||
throw new Error(`cannot unify ${prettyT(fType)} and ${prettyT(aType)}`);
|
throw new Error(`cannot unify ${prettyT(fType)} and ${prettyT(aType)}`);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// console.log("symbols match - unify recursively", formal.symbol);
|
const fTag = fStack.length;
|
||||||
const unifiedParams =
|
const aTag = aStack.length;
|
||||||
zip(fType.params, aType.params)
|
|
||||||
.map(([fParam, aParam]) => unifyInternal(typeVars, fParam, aParam));
|
const unifications =
|
||||||
const {substitutions, deleted} =
|
zip(fType.params, aType.params)
|
||||||
unifiedParams.reduce(({substitutions: s, deleted: d}, cur) => {
|
.map(([getFParam, getAParam]) => {
|
||||||
// console.log('merging', s, cur.substitutions);
|
const fParam = getFParam(fTag);
|
||||||
const {substitutions, deleted} = mergeTwoWay(s, cur.substitutions);
|
const aParam = getAParam(aTag);
|
||||||
return {
|
// type recursively points to an enclosing type that we've already seen
|
||||||
substitutions,
|
if (fStack[fParam] !== aStack[aParam]) {
|
||||||
deleted: deleted.union(d),
|
// note that both are also allowed not to be mapped (undefined)
|
||||||
};
|
throw new Error("cannot unify: types differ in their recursion");
|
||||||
}, { substitutions: new Map(), deleted: new Set() });
|
}
|
||||||
// console.log(pretty({unifiedParams}));
|
if (fStack[fParam] !== undefined) {
|
||||||
return {
|
const type = fStack[fParam];
|
||||||
substitutions,
|
return () => ({
|
||||||
genericType: {
|
substitutions: new Map(),
|
||||||
typeVars: typeVars.difference(substitutions).difference(deleted),
|
genericType: {
|
||||||
type: {
|
typeVars,
|
||||||
symbol: fType.symbol,
|
type,
|
||||||
params: unifiedParams.map(p => p.genericType.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) => {
|
export const unify = (fGenericType, aGenericType) => {
|
||||||
let allTypeVars;
|
let allTypeVars;
|
||||||
[allTypeVars, fGenericType, aGenericType] = safeUnionTypeVars(fGenericType, aGenericType);
|
[allTypeVars, fGenericType, aGenericType] = safeUnionTypeVars(fGenericType, aGenericType);
|
||||||
const {genericType} = unifyInternal(
|
const {genericType} = __unify(allTypeVars, fGenericType.type, aGenericType.type);
|
||||||
allTypeVars,
|
|
||||||
fGenericType.type,
|
|
||||||
aGenericType.type,
|
|
||||||
)
|
|
||||||
return recomputeTypeVars(genericType);
|
return recomputeTypeVars(genericType);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const substitute = (type, substitutions) => {
|
export const substitute = (type, substitutions, stack=[]) => {
|
||||||
// console.log("substitute", {type, substitutions})
|
console.log('substitute...', {type, substitutions, stack});
|
||||||
if (substitutions.has(type)) {
|
return substitutions.get(type)
|
||||||
return substitutions.get(type);
|
|| {
|
||||||
}
|
|
||||||
if (typeof type === "symbol") {
|
|
||||||
return type; // nothing to substitute here
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
symbol: type.symbol,
|
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);
|
[allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType);
|
||||||
const [inType] = genFnType.type.params;
|
const [inType] = genFnType.type.params;
|
||||||
const {substitutions} = unifyInternal(allTypeVars, inType, paramType.type);
|
const {substitutions} = unifyInternal(allTypeVars, inType, paramType.type);
|
||||||
// console.log({genFnType: prettyT(genFnType), paramType: prettyT(paramType), substitutions})
|
|
||||||
const substitutedFnType = substitute(genFnType.type, substitutions);
|
const substitutedFnType = substitute(genFnType.type, substitutions);
|
||||||
return recomputeTypeVars(onlyOccurring(substitutedFnType, allTypeVars));
|
return recomputeTypeVars(onlyOccurring(substitutedFnType, allTypeVars));
|
||||||
}
|
};
|
||||||
|
|
||||||
export const recomputeTypeVars = (genType) => {
|
export const recomputeTypeVars = (genType) => {
|
||||||
const newTypeVars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'].map(Symbol);
|
const newTypeVars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'].map(Symbol);
|
||||||
|
|
@ -195,11 +231,11 @@ export const recomputeTypeVars = (genType) => {
|
||||||
type: substitute(genType.type, subst),
|
type: substitute(genType.type, subst),
|
||||||
};
|
};
|
||||||
return substType;
|
return substType;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const safeUnionTypeVars = (genTypeA, genTypeB) => {
|
export const safeUnionTypeVars = (genTypeA, genTypeB) => {
|
||||||
const substTypeA = recomputeTypeVars(genTypeA);
|
const substTypeA = recomputeTypeVars(genTypeA);
|
||||||
const substTypeB = recomputeTypeVars(genTypeB);
|
const substTypeB = recomputeTypeVars(genTypeB);
|
||||||
const allTypeVars = substTypeA.typeVars.union(substTypeB.typeVars);
|
const allTypeVars = substTypeA.typeVars.union(substTypeB.typeVars);
|
||||||
return [allTypeVars, substTypeA, substTypeB];
|
return [allTypeVars, substTypeA, substTypeB];
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,9 @@ export const ModulePoint = {l:[
|
||||||
{i: PointCartesian2D , t: Type},
|
{i: PointCartesian2D , t: Type},
|
||||||
{i: PointPolar2D , 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
|
// Double -> Double -> PointCartesian2D -> PointCartesian2D
|
||||||
...typedFnType(translate, fnType =>
|
...typedFnType(translate, fnType =>
|
||||||
|
|
|
||||||
|
|
@ -103,10 +103,10 @@ export const ModulePoint = {l:[
|
||||||
{i: getR, t: getRFnType},
|
{i: getR, t: getRFnType},
|
||||||
{i: getΘ, t: getΘFnType},
|
{i: getΘ, t: getΘFnType},
|
||||||
|
|
||||||
...typedFnType(cart2polar, fnType => fnType(NPoint2DCartesian)(NPoint2DPolar)),
|
...typedFnType(cart2polar, fnType => fnType(() => NPoint2DCartesian)(() => NPoint2DPolar)),
|
||||||
...typedFnType(polar2cart, fnType => fnType(NPoint2DPolar)(NPoint2DCartesian)),
|
...typedFnType(polar2cart, fnType => fnType(() => NPoint2DPolar)(() => NPoint2DCartesian)),
|
||||||
|
|
||||||
// ...typedFnType(polar2cart, fnType => fnType(Point2DPolar)(Point2DCartesian)),
|
// ...typedFnType(polar2cart, fnType => fnType(() => Point2DPolar)(() => Point2DCartesian)),
|
||||||
|
|
||||||
// // Double -> Double -> PointCartesian2D -> PointCartesian2D
|
// // Double -> Double -> PointCartesian2D -> PointCartesian2D
|
||||||
// ...typedFnType(translate, fnType =>
|
// ...typedFnType(translate, fnType =>
|
||||||
|
|
@ -118,9 +118,9 @@ export const ModulePoint = {l:[
|
||||||
// (Point2DCartesian)
|
// (Point2DCartesian)
|
||||||
// (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
|
// // Double -> PointPolar2D -> PointPolar2D
|
||||||
// ...typedFnType(rotate, fnType =>
|
// ...typedFnType(rotate, fnType =>
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@ export const ModuleSymbol = {l:[
|
||||||
(String)
|
(String)
|
||||||
),
|
),
|
||||||
|
|
||||||
...typedFnType(eqSymbol, fnType => fnType(SymbolT)(fnType(SymbolT)(Bool))),
|
...typedFnType(eqSymbol, fnType => fnType(() => SymbolT)(fnType(SymbolT)(() => Bool))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ export const ModuleBool = {l:[
|
||||||
{i: Bool , t: Type},
|
{i: Bool , t: Type},
|
||||||
|
|
||||||
// Bool -> Bool -> Bool
|
// Bool -> Bool -> Bool
|
||||||
...typedFnType(eqBool, fnType => fnType(Bool)(fnType(Bool)(Bool))),
|
...typedFnType(eqBool, fnType => fnType(() => Bool)(fnType(Bool)(() => Bool))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,5 @@ const eqByte = x => y => x === y;
|
||||||
export const ModuleByte = {l:[
|
export const ModuleByte = {l:[
|
||||||
{i: Byte , t: Type },
|
{i: Byte , t: Type },
|
||||||
|
|
||||||
...typedFnType(eqByte, fnType => fnType(Byte)(fnType(Byte)(Bool))),
|
...typedFnType(eqByte, fnType => fnType(() => Byte)(fnType(Byte)(() => Bool))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export const eqDouble = x => y => x === y;
|
||||||
export const ModuleDouble = {l:[
|
export const ModuleDouble = {l:[
|
||||||
{i: Double, t: Type},
|
{i: Double, t: Type},
|
||||||
|
|
||||||
...typedFnType(addDouble, fnType => fnType(Double)(fnType(Double)(Double))),
|
...typedFnType(addDouble, fnType => fnType(() => Double)(fnType(Double)(() => Double))),
|
||||||
...typedFnType(mulDouble, fnType => fnType(Double)(fnType(Double)(Double))),
|
...typedFnType(mulDouble, fnType => fnType(() => Double)(fnType(Double)(() => Double))),
|
||||||
...typedFnType(eqDouble, fnType => fnType(Double)(fnType(Double)(Bool))),
|
...typedFnType(eqDouble, fnType => fnType(() => Double)(fnType(Double)(() => Bool))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { typedFnType } from "../structures/types.js";
|
import { typedFnType } from "../structures/types.js";
|
||||||
import { Any, Type } from "./types.js";
|
import { Top, Type } from "./types.js";
|
||||||
import { makeTypeConstructor } from "../type_constructor.js";
|
import { makeTypeConstructor } from "../type_constructor.js";
|
||||||
|
|
||||||
// A type-link, connecting a value to its Type.
|
// A type-link, connecting a value to its Type.
|
||||||
|
|
@ -11,9 +11,9 @@ export const getType = lnk => lnk.t;
|
||||||
|
|
||||||
export const ModuleDynamic = {l:[
|
export const ModuleDynamic = {l:[
|
||||||
{i: Dynamic, t: Type},
|
{i: Dynamic, t: Type},
|
||||||
{i: Any , t: Type},
|
{i: Top , t: Type},
|
||||||
|
|
||||||
...typedFnType(getInst, fnType => fnType(Dynamic)(Any)),
|
...typedFnType(getInst, fnType => fnType(() => Dynamic)(() => Top)),
|
||||||
...typedFnType(getType, fnType => fnType(Dynamic)(Any)),
|
...typedFnType(getType, fnType => fnType(() => Dynamic)(() => Top)),
|
||||||
]};
|
]};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { newLeft, newRight } from "../structures/sum.js";
|
import { newLeft, newRight } from "../structures/sum.js";
|
||||||
import { setType, sumType, typedFnType } from "../structures/types.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";
|
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)
|
||||||
: newLeft(unit);
|
: newLeft(unit);
|
||||||
|
|
||||||
export const ModuleGenericType = {l:[
|
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))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const deserialize = str => BigInt(str);
|
||||||
export const ModuleInt = {l:[
|
export const ModuleInt = {l:[
|
||||||
{i: Int , t: Type },
|
{i: Int , t: Type },
|
||||||
|
|
||||||
...typedFnType(addInt, fnType => fnType(Int)(fnType(Int)(Int))),
|
...typedFnType(addInt, fnType => fnType(() => Int)(fnType(Int)(() => Int))),
|
||||||
...typedFnType(mulInt, fnType => fnType(Int)(fnType(Int)(Int))),
|
...typedFnType(mulInt, fnType => fnType(() => Int)(fnType(Int)(() => Int))),
|
||||||
...typedFnType(eqInt , fnType => fnType(Int)(fnType(Int)(Bool))),
|
...typedFnType(eqInt , fnType => fnType(() => Int)(fnType(Int)(() => Bool))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ export const ModuleType = {l:[
|
||||||
(Bool)
|
(Bool)
|
||||||
)),
|
)),
|
||||||
|
|
||||||
...typedFnType(getSymbol, fnType => fnType(Type)(SymbolT)),
|
...typedFnType(getSymbol, fnType => fnType(() => Type)(() => SymbolT)),
|
||||||
...typedFnType(getParams, fnType => fnType(Type)(lsType(Type))),
|
...typedFnType(getParams, fnType => fnType(() => Type)(() => lsType(() =>Type))),
|
||||||
|
|
||||||
...typedFnType(isFunction, fnType => fnType(Type)(Bool)),
|
...typedFnType(isFunction, fnType => fnType(() => Type)(() => Bool)),
|
||||||
]};
|
]};
|
||||||
|
|
@ -2,16 +2,16 @@
|
||||||
|
|
||||||
import { makeTypeConstructor } from "../type_constructor.js";
|
import { makeTypeConstructor } from "../type_constructor.js";
|
||||||
|
|
||||||
export const SymbolInt = Symbol('Int');
|
export const SymbolInt = Symbol('Int');
|
||||||
export const SymbolBool = Symbol('Bool');
|
export const SymbolBool = Symbol('Bool');
|
||||||
export const SymbolDouble = Symbol('Double');
|
export const SymbolDouble = Symbol('Double');
|
||||||
export const SymbolByte = Symbol('Byte');
|
export const SymbolByte = Symbol('Byte');
|
||||||
export const SymbolChar = Symbol('Char');
|
export const SymbolChar = Symbol('Char');
|
||||||
export const SymbolUnit = Symbol('Unit');
|
export const SymbolUnit = Symbol('Unit');
|
||||||
export const SymbolBottom = Symbol('⊥');
|
export const SymbolBottom = Symbol('⊥');
|
||||||
export const SymbolSymbol = Symbol('Symbol');
|
export const SymbolSymbol = Symbol('Symbol');
|
||||||
export const SymbolType = Symbol('Type');
|
export const SymbolType = Symbol('Type');
|
||||||
export const symbolAny = Symbol('Any');
|
export const symbolTop = Symbol('⊤');
|
||||||
export const SymbolGenericType = Symbol('GenericType');
|
export const SymbolGenericType = Symbol('GenericType');
|
||||||
|
|
||||||
export const Int = makeTypeConstructor(SymbolInt)(0);
|
export const Int = makeTypeConstructor(SymbolInt)(0);
|
||||||
|
|
@ -28,23 +28,24 @@ export const Bottom = makeTypeConstructor(SymbolBottom)(0);
|
||||||
|
|
||||||
export const SymbolT = makeTypeConstructor(SymbolSymbol)(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 Type = makeTypeConstructor(SymbolType)(0);
|
||||||
|
|
||||||
export const GenericType = makeTypeConstructor(SymbolGenericType)(0);
|
export const GenericType = makeTypeConstructor(SymbolGenericType)(0);
|
||||||
|
|
||||||
// Everything is typed by Any
|
// Everything is typed by Top
|
||||||
export const Any = makeTypeConstructor(symbolAny)(0);
|
export const Top = makeTypeConstructor(symbolTop)(0);
|
||||||
|
|
||||||
export const ModuleSymbols = {l:[
|
export const ModuleSymbols = {l:[
|
||||||
{i: SymbolInt , t: SymbolT},
|
{i: SymbolInt , t: SymbolT},
|
||||||
{i: SymbolBool , t: SymbolT},
|
{i: SymbolBool , t: SymbolT},
|
||||||
{i: SymbolDouble, t: SymbolT},
|
{i: SymbolDouble , t: SymbolT},
|
||||||
{i: SymbolByte , t: SymbolT},
|
{i: SymbolByte , t: SymbolT},
|
||||||
{i: SymbolChar , t: SymbolT},
|
{i: SymbolChar , t: SymbolT},
|
||||||
{i: SymbolUnit , t: SymbolT},
|
{i: SymbolUnit , t: SymbolT},
|
||||||
{i: SymbolBottom, t: SymbolT},
|
{i: SymbolBottom , t: SymbolT},
|
||||||
{i: SymbolSymbol, t: SymbolT},
|
{i: SymbolSymbol , t: SymbolT},
|
||||||
{i: SymbolType , t: SymbolT},
|
{i: SymbolType , t: SymbolT},
|
||||||
{i: SymbolGenericType, t: SymbolT},
|
{i: SymbolGenericType, t: SymbolT},
|
||||||
|
{i: symbolTop , t: SymbolT},
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ export const ModuleUnit = {l:[
|
||||||
{i: Unit, t: Type},
|
{i: Unit, t: Type},
|
||||||
|
|
||||||
// Unit -> Unit -> Bool
|
// Unit -> Unit -> Bool
|
||||||
...typedFnType(eqUnit, fnType => fnType(Unit)(fnType(Unit)(Bool))),
|
...typedFnType(eqUnit, fnType => fnType(() => Unit)(fnType(Unit)(() => Bool))),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export const enumType = variants => {
|
||||||
}
|
}
|
||||||
const [variant, ...rest] = variants;
|
const [variant, ...rest] = variants;
|
||||||
const variantType = getRight(variant);
|
const variantType = getRight(variant);
|
||||||
return sumType(variantType)(enumType(rest));
|
return sumType(() => variantType)(() => enumType(rest));
|
||||||
};
|
};
|
||||||
|
|
||||||
const eatParameters = (numParams, result) => {
|
const eatParameters = (numParams, result) => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { Dynamic } from "../primitives/dynamic.js"
|
||||||
|
|
||||||
// 'normal' implementation
|
// 'normal' implementation
|
||||||
export const emptyList = {l:[]};
|
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 get = ls => i => ls.l[i];
|
||||||
export const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)});
|
export const put = ls => i => elem => ({l: ls.l.with(Number(i), elem)});
|
||||||
export const push = ls => elem => ({l:ls.l.concat([elem])});
|
export const push = ls => elem => ({l:ls.l.concat([elem])});
|
||||||
|
|
@ -21,62 +21,62 @@ export const fold = ls => callback => initial => {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const String = lsType(Char); // alias
|
export const String = lsType(() =>Char); // alias
|
||||||
export const Module = lsType(Dynamic);
|
export const Module = lsType(() =>Dynamic);
|
||||||
|
|
||||||
export const ModuleList = {l:[
|
export const ModuleList = {l:[
|
||||||
// Type -> Type
|
// // Type -> Type
|
||||||
...typedFnType(lsType, fnType =>
|
// ...typedFnType(lsType, fnType =>
|
||||||
fnType
|
// fnType
|
||||||
/* in */ (Type)
|
// /* in */ (Type)
|
||||||
/* out */ (Type)
|
// /* out */ (Type)
|
||||||
),
|
// ),
|
||||||
|
|
||||||
// [a]
|
// // [a]
|
||||||
{i: emptyList, t: emptyListType},
|
// // {i: emptyList, t: emptyListType},
|
||||||
{i: emptyListType, t: GenericType},
|
// // {i: emptyListType, t: GenericType},
|
||||||
|
|
||||||
// [a] -> Int -> a
|
// // [a] -> Int -> a
|
||||||
...typedFnType(get, fnType =>
|
// ...typedFnType(get, fnType =>
|
||||||
makeGeneric(a =>
|
// makeGeneric(a =>
|
||||||
fnType
|
// fnType
|
||||||
/* in */ (lsType(a))
|
// /* in */ (() => lsType(() => a))
|
||||||
/* out */ (fnType
|
// /* out */ (() => fnType
|
||||||
/* in */ (Int)
|
// /* in */ (() => Int)
|
||||||
/* out */ (a)
|
// /* out */ (() => a)
|
||||||
)), GenericType),
|
// )), GenericType),
|
||||||
|
|
||||||
// [a] -> Int -> a -> [a]
|
// // [a] -> Int -> a -> [a]
|
||||||
...typedFnType(put, fnType =>
|
// ...typedFnType(put, fnType =>
|
||||||
makeGeneric(a =>
|
// makeGeneric(a =>
|
||||||
fnType
|
// fnType
|
||||||
/* in */ (lsType(a))
|
// /* in */ (() => lsType(() => a))
|
||||||
/* out */ (fnType
|
// /* out */ (() => fnType
|
||||||
/* in */ (Int)
|
// /* in */ (() => Int)
|
||||||
/* out */ (fnType
|
// /* out */ (() => fnType
|
||||||
/* in */ (a)
|
// /* in */ (() => a)
|
||||||
/* out */ (lsType(a))
|
// /* out */ (() => lsType(() => a))
|
||||||
)
|
// )
|
||||||
)), GenericType),
|
// )), GenericType),
|
||||||
|
|
||||||
// [a] -> a -> [a]
|
// // [a] -> a -> [a]
|
||||||
...typedFnType(push, fnType =>
|
// ...typedFnType(push, fnType =>
|
||||||
makeGeneric(a =>
|
// makeGeneric(a =>
|
||||||
fnType
|
// fnType
|
||||||
(lsType(a))
|
// (() => lsType(() => a))
|
||||||
(fnType
|
// (() => fnType
|
||||||
(a)
|
// (() => a)
|
||||||
(lsType(a))
|
// (() => lsType(() => a))
|
||||||
)
|
// )
|
||||||
), GenericType),
|
// ), GenericType),
|
||||||
|
|
||||||
// [a] -> (a -> b) -> [b]
|
// // [a] -> (a -> b) -> [b]
|
||||||
...typedFnType(map, fnType =>
|
// ...typedFnType(map, fnType =>
|
||||||
makeGeneric((a, b) =>
|
// makeGeneric((a, b) =>
|
||||||
fnType
|
// fnType
|
||||||
(lsType(a))
|
// (() => lsType(() => a))
|
||||||
(fnType
|
// (() => fnType
|
||||||
(fnType(a)(b))
|
// (() => fnType(() => a)(() => b))
|
||||||
(lsType(b))
|
// (() => lsType(() => b))
|
||||||
)), GenericType),
|
// )), GenericType),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export const ModuleProduct = {l: [
|
||||||
(a)
|
(a)
|
||||||
(fnType
|
(fnType
|
||||||
(b)
|
(b)
|
||||||
(prodType(a)(b))
|
(prodType(() => a)(() => b))
|
||||||
)
|
)
|
||||||
), GenericType),
|
), GenericType),
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ export const ModuleProduct = {l: [
|
||||||
...typedFnType(getLeft, fnType =>
|
...typedFnType(getLeft, fnType =>
|
||||||
makeGeneric((a, b) =>
|
makeGeneric((a, b) =>
|
||||||
fnType
|
fnType
|
||||||
(prodType(a)(b))
|
(prodType(() => a)(() => b))
|
||||||
(a)
|
(a)
|
||||||
), GenericType),
|
), GenericType),
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const ModuleProduct = {l: [
|
||||||
...typedFnType(getRight, fnType =>
|
...typedFnType(getRight, fnType =>
|
||||||
makeGeneric((a, b) =>
|
makeGeneric((a, b) =>
|
||||||
fnType
|
fnType
|
||||||
(prodType(a)(b))
|
(prodType(() => a)(() => b))
|
||||||
(b)
|
(b)
|
||||||
), GenericType),
|
), GenericType),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export class RBTreeWrapper {
|
||||||
// (a -> a -> Int) -> Set(a)
|
// (a -> a -> Int) -> Set(a)
|
||||||
export const emptySet = compareFn => new RBTreeWrapper(createRBTree((x, y) => compareFn(x)(y)));
|
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 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 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 =>
|
// ...typedFnType(has, fnType =>
|
||||||
// makeGeneric(a =>
|
// makeGeneric(a =>
|
||||||
// fnType
|
// fnType
|
||||||
// /* in */ (setType(a))
|
// /* in */ (set(() => a))
|
||||||
// /* out */ (fnType
|
// /* out */ (fnType
|
||||||
// /* in */ (a)
|
// /* in */ (a)
|
||||||
// /* out */ (Bool)
|
// /* out */ (Bool)
|
||||||
|
|
@ -66,9 +66,9 @@ export const ModuleSet = {l:[
|
||||||
// ...typedFnType(add, fnType =>
|
// ...typedFnType(add, fnType =>
|
||||||
// makeGeneric(a =>
|
// makeGeneric(a =>
|
||||||
// fnType
|
// fnType
|
||||||
// /* in */ (setType(a))
|
// /* in */ (set(() => a))
|
||||||
// /* out */ (fnType
|
// /* out */ (fnType
|
||||||
// /* in */ (a)
|
// /* in */ (a)
|
||||||
// /* out */ (setType(a))
|
// /* out */ (set(() => a))
|
||||||
// )), GenericType),
|
// )), GenericType),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const structType = fields => {
|
||||||
}
|
}
|
||||||
const [field, ...rest] = fields;
|
const [field, ...rest] = fields;
|
||||||
const fieldType = getRight(field);
|
const fieldType = getRight(field);
|
||||||
return prodType(fieldType)(structType(rest));
|
return prodType(() => fieldType)(() => structType(rest));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const makeConstructor = fields => {
|
export const makeConstructor = fields => {
|
||||||
|
|
@ -41,7 +41,7 @@ export const makeConstructorType = type => fields => {
|
||||||
}
|
}
|
||||||
const [field, ...rest] = fields;
|
const [field, ...rest] = fields;
|
||||||
const fieldType = getRight(field);
|
const fieldType = getRight(field);
|
||||||
return fnType(fieldType)(makeConstructorType(rest));
|
return fnType(() => fieldType)(() => makeConstructorType(rest));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const makeGetters = fields => {
|
export const makeGetters = fields => {
|
||||||
|
|
@ -60,6 +60,6 @@ export const makeGetters = fields => {
|
||||||
export const makeGettersTypes = type => fields => {
|
export const makeGettersTypes = type => fields => {
|
||||||
return fields.map(field => {
|
return fields.map(field => {
|
||||||
const fieldType = getRight(field);
|
const fieldType = getRight(field);
|
||||||
return fnType(type)(fieldType);
|
return fnType(() => type)(() => fieldType);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -23,40 +23,40 @@ export const ModuleSum = {l:[
|
||||||
// Type -> Type -> Type
|
// Type -> Type -> Type
|
||||||
...typedFnType(sumType, fnType =>
|
...typedFnType(sumType, fnType =>
|
||||||
fnType
|
fnType
|
||||||
(Type)
|
(() => Type)
|
||||||
(fnType
|
(() => fnType
|
||||||
(Type)
|
(() => Type)
|
||||||
(Type)
|
(() => Type)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// a -> a | b
|
// // a -> a | b
|
||||||
...typedFnType(newLeft, fnType =>
|
// ...typedFnType(newLeft, fnType =>
|
||||||
makeGeneric((a, b) =>
|
// makeGeneric((a, b) =>
|
||||||
fnType
|
// fnType
|
||||||
(a)
|
// (a)
|
||||||
(sumType(a)(b))
|
// (sumType(() => a)(() => b))
|
||||||
), GenericType),
|
// ), GenericType),
|
||||||
|
|
||||||
// b -> a | b
|
// // b -> a | b
|
||||||
...typedFnType(newRight, fnType =>
|
// ...typedFnType(newRight, fnType =>
|
||||||
makeGeneric((a, b) =>
|
// makeGeneric((a, b) =>
|
||||||
fnType
|
// fnType
|
||||||
(b)
|
// (b)
|
||||||
(sumType(a)(b))
|
// (sumType(() => a)(() => b))
|
||||||
), GenericType),
|
// ), GenericType),
|
||||||
|
|
||||||
// a | b -> (a -> c, b-> c) -> c
|
// // a | b -> (a -> c, b-> c) -> c
|
||||||
...typedFnType(match, fnType =>
|
// ...typedFnType(match, fnType =>
|
||||||
makeGeneric((a, b, c) =>
|
// makeGeneric((a, b, c) =>
|
||||||
fnType
|
// fnType
|
||||||
(sumType(a)(b))
|
// (() => sumType(() => a)(() => b))
|
||||||
(fnType
|
// (() => fnType
|
||||||
(prodType
|
// (() => prodType
|
||||||
(fnType(a)(c))
|
// (() => fnType(() => a)(() => c))
|
||||||
(fnType(b)(c))
|
// (() => fnType(() => b)(() => c))
|
||||||
)
|
// )
|
||||||
(c)
|
// (() => c)
|
||||||
)
|
// )
|
||||||
), GenericType),
|
// ), GenericType),
|
||||||
]};
|
]};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export const isFunction = type => getSymbol(type) === symbolFunction;
|
||||||
export const typedFnType = (instance, callback, typeOfType = Type) => {
|
export const typedFnType = (instance, callback, typeOfType = Type) => {
|
||||||
const fnTs = [];
|
const fnTs = [];
|
||||||
const wrappedFnType = inType => outType => {
|
const wrappedFnType = inType => outType => {
|
||||||
const fnT = fnType(inType)(outType);
|
const fnT = fnType(() => inType)(() => outType);
|
||||||
fnTs.push(fnT);
|
fnTs.push(fnT);
|
||||||
return fnT;
|
return fnT;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,55 @@
|
||||||
import { DefaultMap } from "./util/defaultmap.js";
|
// import { DefaultMap } from "./util/defaultmap.js";
|
||||||
|
|
||||||
const nullaryTypeConstructors = new DefaultMap(
|
// const nullaryTypeConstructors = new DefaultMap(
|
||||||
// symbol -> 0-ary type constructor (= a type, basically)
|
// // symbol -> 0-ary type constructor (= a type, basically)
|
||||||
symbol => ({
|
// symbol => ({
|
||||||
symbol,
|
// symbol,
|
||||||
params: [],
|
// params: [],
|
||||||
}));
|
// }));
|
||||||
|
|
||||||
const makeTypeConstructorInternal = (symbol, n_ary, params = []) => {
|
|
||||||
// console.log("n_ary:", n_ary);
|
// // nAry: how many more type parameters to take
|
||||||
if (n_ary === 0 || n_ary === 0n) {
|
// // params: the type params we already took
|
||||||
// a bit dirty, but otherwise OK
|
// const makeTypeConstructorInternal = (symbol, nAry, params = []) => {
|
||||||
if (params.length > 0) {
|
// // console.log("n_ary:", n_ary);
|
||||||
const result = { symbol, params };
|
// if (nAry === 0 || nAry === 0n) {
|
||||||
// console.log("result:", result);
|
// // a bit dirty, but otherwise OK
|
||||||
return result;
|
// if (params.length > 0) {
|
||||||
}
|
// const result = { symbol, params };
|
||||||
else {
|
// // console.log("result:", result);
|
||||||
const result = nullaryTypeConstructors.getdefault(symbol, true)
|
// return result;
|
||||||
// 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 {
|
return typeParam => {
|
||||||
const m = new DefaultMap(typeParam => makeTypeConstructorInternal(symbol, n_ary - 1, params.concat([typeParam])));
|
if (typeof typeParam !== 'function') {
|
||||||
const fnName = 'make'+symbol.description+'Type';
|
throw new Error("all type params must be functions");
|
||||||
return {
|
}
|
||||||
[fnName]: typeParam => m.getdefault(typeParam, true),
|
return __makeTypeConstructor(symbol, nAry-1, params.concat([typeParam]));
|
||||||
}[fnName];
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Creates a new nominal type
|
// 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 getSymbol = type => type.symbol;
|
||||||
export const getParams = type => ({ l: type.params });
|
export const getParams = type => ({ l: type.params });
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export const getEq = numDict => numDict.eq;
|
||||||
|
|
||||||
export const ModuleEq = {l:[
|
export const ModuleEq = {l:[
|
||||||
// type constructor: Type -> Type
|
// type constructor: Type -> Type
|
||||||
...typedFnType(eqDictType, fnType => fnType(Type)(Type)),
|
...typedFnType(eqDictType, fnType => fnType(() => Type)(() => Type)),
|
||||||
|
|
||||||
// (EqDict a) -> a -> a -> Bool
|
// (EqDict a) -> a -> a -> Bool
|
||||||
...typedFnType(getEq, fnType =>
|
...typedFnType(getEq, fnType =>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const [getAddMulFnType, typesOfFns] = typedFnType2(fnType =>
|
||||||
(numDictType(a))
|
(numDictType(a))
|
||||||
(fnType
|
(fnType
|
||||||
(a)
|
(a)
|
||||||
(fnType(a)(a))
|
(fnType(() => a)(() => a))
|
||||||
)));
|
)));
|
||||||
|
|
||||||
export const ModuleNum = {l:[
|
export const ModuleNum = {l:[
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,43 @@
|
||||||
import { inspect } from 'node:util';
|
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) {
|
export function pretty(obj) {
|
||||||
return inspect(obj, { colors: true, depth: null, breakLength: 120 });
|
return inspect(obj, { colors: true, depth: null, breakLength: 120 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretty print type
|
// Pretty print type
|
||||||
export function prettyT(type) {
|
export const prettyT = type => {
|
||||||
// console.log("pretty:", type);
|
return mapRecursiveStructure(([type, m, seen], map) => {
|
||||||
if (typeof type === "symbol") {
|
if (typeof type === "symbol") {
|
||||||
return type.description;
|
return type.description;
|
||||||
}
|
}
|
||||||
if (type.typeVars) {
|
if (!m.has(type)) {
|
||||||
if (type.typeVars.size > 0) {
|
m.add(type); // next time we encounter this type, we'll only render a placeholder
|
||||||
return `∀${[...type.typeVars].map(prettyT).sort((a, b) => a.localeCompare(b)).join(",")}: ${prettyT(type.type)}`;
|
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 {
|
else {
|
||||||
return prettyT(type.type);
|
if (!seen.has(type)) {
|
||||||
|
seen.set(type, `#${seen.size}`);
|
||||||
|
}
|
||||||
|
return seen.get(type);
|
||||||
}
|
}
|
||||||
}
|
})([type, new Set(), new Map()])();
|
||||||
if (type.symbol === symbolFunction) {
|
};
|
||||||
return `(${prettyT(type.params[0])} -> ${prettyT(type.params[1])})`;
|
|
||||||
}
|
const renderType = (symbol, annot, params) => {
|
||||||
if (type.symbol === symbolList) {
|
return {
|
||||||
return `[${prettyT(type.params[0])}]`;
|
[symbolList] : `${annot}[${params[0]}]`,
|
||||||
}
|
[symbolSet] : `${annot}{${params[0]}}`,
|
||||||
if (type.symbol === symbolProduct) {
|
[symbolFunction]: `${annot}(${params[0]} -> ${params[1]})`,
|
||||||
return `(${prettyT(type.params[0])} × ${prettyT(type.params[1])})`;
|
[symbolSum] : `${annot}(${params[0]} + ${params[1]})`,
|
||||||
}
|
[symbolProduct] : `${annot}(${params[0]} ⨯ ${params[1]})`,
|
||||||
if (type.symbol === symbolSum) {
|
}[symbol] || symbol.description;
|
||||||
return `(${prettyT(type.params[0])} | ${prettyT(type.params[1])})`;
|
};
|
||||||
}
|
|
||||||
if (type.params.length === 0) {
|
export const prettyGenT = genericType => {
|
||||||
return type.symbol.description;
|
return `∀${[...genericType.typeVars].map(prettyT).sort((a, b) => a.localeCompare(b)).join(",")}: ${prettyT(genericType.type)}`;
|
||||||
}
|
};
|
||||||
return `${type.symbol.description}(${type.params.map(prettyT).join(", ")})`;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
52
util/util.js
52
util/util.js
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { lsType, setType } from "../structures/types.js";
|
||||||
|
import { pretty } from "./pretty.js";
|
||||||
|
|
||||||
// re-inventing the wheel:
|
// re-inventing the wheel:
|
||||||
export function deepEqual(a, b) {
|
export function deepEqual(a, b) {
|
||||||
|
|
@ -46,4 +48,54 @@ export function capitalizeFirstLetter(val) {
|
||||||
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
|
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]();
|
||||||
|
// // }
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
|
import { fnType } from "../structures/types.js";
|
||||||
import { deepEqual } from "../util/util.js";
|
import { deepEqual } from "../util/util.js";
|
||||||
import { inspect } from "node:util";
|
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>
|
// a -> Value<a>
|
||||||
export const newLiteral = val => ({
|
export const newLiteral = val => ({
|
||||||
kind: "literal",
|
kind: "literal",
|
||||||
|
|
@ -44,7 +40,7 @@ export const transform = input => fn => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Value<a> -> Set<Slot<Any>>
|
// Value<a> -> Set<Slot<Top>>
|
||||||
export const getReadDependencies = value => {
|
export const getReadDependencies = value => {
|
||||||
if (value.kind === "literal") {
|
if (value.kind === "literal") {
|
||||||
return new Set();
|
return new Set();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue