diff --git a/examples/rbtree_bench.js b/benchmarks/rbtree_bench.js similarity index 100% rename from examples/rbtree_bench.js rename to benchmarks/rbtree_bench.js diff --git a/compare/registry.js b/compare/registry.js deleted file mode 100644 index dc2342e..0000000 --- a/compare/registry.js +++ /dev/null @@ -1,32 +0,0 @@ -import { getInst, getType } from "../primitives/dynamic.js"; -import { SymbolBool, SymbolChar, SymbolDouble, SymbolInt, SymbolType, SymbolUnit } from "../primitives/types.js"; -import { symbolList, symbolProduct, symbolSet, symbolSum } from "../structures/types.js"; -import { compareBools, compareNumbers, compareStrings, compareUnits } from "./primitives.js"; -import { compareLists, compareProducts, compareSets, compareSums } from "./structures.js"; -import { compareTypes } from "./type.js"; - -const typeSymbolToCmp = new Map([ - [SymbolInt , compareNumbers], - [SymbolChar , compareStrings], - [SymbolDouble, compareNumbers], - [SymbolBool , compareBools], - [SymbolUnit , compareUnits], - [SymbolType , compareTypes], - - // these functions take extra comparison callbacks: - [symbolList , compareLists], - [symbolProduct, compareProducts], - [symbolSum , compareSums], - [symbolSet , compareSets], -]); - -export const makeCompareFn = type => { - return type.params.reduce( - (acc, cur) => acc(makeCompareFn(cur)), - typeSymbolToCmp.get(type.symbol) - ); -}; - -export const compareDynamic = x => y => - compareTypes(getType(x))(getType(y)) - || makeCompareFn(getType(x))(getInst(x))(getInst(y)); diff --git a/examples/enum.js b/examples/enum.js index 43223f4..8ca9301 100644 --- a/examples/enum.js +++ b/examples/enum.js @@ -1,13 +1,15 @@ -import { makeCompareFn } from "../compare/registry.js"; -import { Int, Unit } from "../primitives/types.js"; -import { unit } from "../primitives/unit.js"; -import { enumType, makeConstructors, makeMatchFn } from "../structures/enum.js"; -import { newProduct } from "../structures/product.js"; -import { lsType, prettyT } from "../structures/types.js"; +import { makeCompareFn } from "../lib/compare/registry.js"; +import { Int, Unit } from "../lib/primitives/primitive_types.js"; +import { unit } from "../lib/primitives/unit.js"; +import { makeConstructors, makeMatchFn } from "../lib/structures/enum.js"; +import { enumType } from "../lib/structures/enum.types.js"; +import { newProduct } from "../lib/structures/product.js"; +import { lsType } from "../lib/structures/type_constructors.js"; +import { prettyT } from "../lib/util/pretty.js"; const variants = [ newProduct("price")(Int), - newProduct("prices")(lsType(() =>Int)), + newProduct("prices")(lsType(_ => Int)), newProduct("not_found")(Unit), ]; @@ -19,7 +21,7 @@ console.log(" ", prettyT(myEnumType)); const [newPrice, newPrices, newNotFound] = makeConstructors(variants); const price = newPrice(10); -const prices = newPrices({ l: [20, 30] }); +const prices = newPrices([20, 30]); const notFound = newNotFound(unit); console.log("observe the encoding of different variant instances:"); @@ -29,7 +31,7 @@ console.log(" ", notFound); const myEnumToString = x => makeMatchFn(variants)(x) (price => `Price: ${price}`) - (prices => `Prices: ${prices.l}`) + (prices => `Prices: ${prices}`) (() => "Not found!"); console.log("observe the generated match function in action:"); @@ -40,9 +42,9 @@ console.log(" ", myEnumToString(notFound)); const compareMyEnum = makeCompareFn(myEnumType); console.log("observe the generated compare function in action:"); -console.log(" smaller ->", compareMyEnum(price)(prices)); -console.log(" bigger ->", compareMyEnum(prices)(price)); -console.log(" bigger ->", compareMyEnum(notFound)(price)); -console.log(" equal ->", compareMyEnum(prices)(prices)); -console.log(" smaller ->", compareMyEnum(newPrice(5))(newPrice(6))); -console.log(" bigger ->", compareMyEnum(newPrices({ l: [5, 6] }))(newPrices({ l: [5, 5] }))); +console.log(" should be smaller ->", compareMyEnum(price)(prices)); +console.log(" should be bigger ->", compareMyEnum(prices)(price)); +console.log(" should be bigger ->", compareMyEnum(notFound)(price)); +console.log(" should be equal ->", compareMyEnum(prices)(prices)); +console.log(" should be smaller ->", compareMyEnum(newPrice(5))(newPrice(6))); +console.log(" should be bigger ->", compareMyEnum(newPrices([5, 6]))(newPrices([5, 5]))); diff --git a/examples/generics.js b/examples/generics.js index e307537..c60ba62 100644 --- a/examples/generics.js +++ b/examples/generics.js @@ -1,45 +1,37 @@ -import { Bool, Int } from "../primitives/types.js"; -import { fnType, lsType } from "../structures/types.js"; -import { assign, makeGeneric, unify } from "../generics/generics.js"; -import { prettyGenT, prettyT } from "../util/pretty.js"; +import { assign, makeGeneric, unify } from "../lib/generics/generics.js"; +import { prettyGenT } from "../lib/util/pretty.js"; +import { getDefaultTypeParser } from "../lib/parser/type_parser.js"; + +const mkType = getDefaultTypeParser(); -// 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)); -console.log((prettyGenT(Bool_to_Int))); // ∀: ([Bool] -> Int) console.log("should be: [Bool] -> Int") -console.log(prettyGenT(unify(a_to_Int, Bool_to_Int))); +console.log(prettyGenT( + unify( + mkType("∀a: (a -> Int)"), + makeGeneric(() => mkType("[Bool] -> Int")), + ) +)); -// (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)); console.log("should be: (Bool -> Bool) -> a"); -console.log(prettyT(unify(fnType2, fnType3))); +console.log(prettyGenT( + unify( + mkType("∀a,b: (a -> a) -> b"), + mkType("∀a: (Bool -> Bool) -> a"), + ) +)); -// (a -> b) -> [a] -> [b] -const mapFnType = makeGeneric((a,b) => - fnType - (fnType(() => a)(() => b)) - (fnType(() => lsType(() =>a))(() => lsType(() =>b)))) -// a -> a -const idFnType = makeGeneric((_,__,c) => - fnType(() => c)(() => c)); -console.log("should be: [c] -> [c]"); -console.log(prettyT(assign(mapFnType, idFnType))); +console.log("should be: [a] -> [a]"); +console.log(prettyGenT( + assign( + mkType("∀a,b: (a -> b) -> [a] -> [b]"), + mkType("∀a: a -> a") + ) +)); -// (a -> Int) -> [a] -> a -const weirdFnType = makeGeneric(a => - fnType - (fnType(() => a)(() => Int)) - (fnType - (lsType(() =>a)) - (a))) -// we call this function with parameter of type (b -> b) ... -// giving these substitutions: -// a := b -// b := Int console.log("should be: [Int] -> Int"); -console.log(prettyT(assign(weirdFnType, idFnType))); +console.log(prettyGenT( + assign( + mkType("∀a: (a -> Int) -> [a] -> a"), + mkType("∀a: a -> a") + ) +)); diff --git a/examples/int_or_bool.js b/examples/int_or_bool.js deleted file mode 100644 index 4f773f9..0000000 --- a/examples/int_or_bool.js +++ /dev/null @@ -1,60 +0,0 @@ -import { assign, makeGeneric, unify } from "../generics/generics.js"; -import { Bool, Int } from "../primitives/types.js"; -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); - - -// console.log(int5); - -console.log(pretty(unify( - makeGeneric(() => IntOrBool), - makeGeneric(a => sumType(() => Int)(() => a)), -))); - -const cipFunction = (x) => { - return match(x)({ - left: x_as_int => (x_as_int === 5), - right: x_as_bool => false, - }); -} - -const cipFunctionType = fnType - (IntOrBool) // in - (Bool); - -// console.log(cipFunctionType); -// console.log(IntOrBool); - -console.log(assign( - makeGeneric(() => cipFunctionType), - makeGeneric(() => IntOrBool), -)); - -console.log("calling newLeft with Int:"); -const typeAtCallSite = assign( - makeGeneric((a, b) => - fnType - (a) - (sumType(() => a)(() => b)) - ), - makeGeneric(() => Int)); -console.log(pretty(typeAtCallSite)); - - -console.log("calling cipFunction:"); -console.log(pretty(assign( - makeGeneric(() => cipFunctionType), - typeAtCallSite, -))); - -console.log("valid function calls:"); -console.log(cipFunction(newLeft(5))); -console.log(cipFunction(newLeft(7))); -console.log(cipFunction(newRight(true))); - -console.log("invalid function calls:"); -console.log(cipFunction(5)); -console.log(cipFunction(newLeft("abc"))); diff --git a/examples/num.js b/examples/num.js deleted file mode 100644 index 0fe79f4..0000000 --- a/examples/num.js +++ /dev/null @@ -1,28 +0,0 @@ -import { assign } from "../generics/generics.js"; -import { makeGeneric } from "../generics/generics.js"; -import { Double, Int } from "../primitives/types.js"; -import { fnType } from "../structures/types.js"; -import { pretty } from '../util/pretty.js'; -import { getMul, NumInstances } from "../typeclasses/num.js"; -import { numDictType } from "./num_type.js"; - -const square = numDict => x => getMul(numDict)(x)(x); - -// NumDict a -> a -> a -const squareFnType = makeGeneric(a => - fnType - (numDictType(a)) - (fnType(() => a)(() => a)) - ); - -console.log("should be: Int -> Int"); -console.log(pretty(assign(squareFnType, makeGeneric(() => numDictType(Int))))); - -console.log("should be: Double -> Double"); -console.log(pretty(assign(squareFnType, makeGeneric(() => numDictType(Double))))); - -// to call 'square' we need: -// - the type of our argument (=Int) -// - access to a mapping from types to their typeclass instantiation -console.log(""); -console.log(square(NumInstances.get(Int))(42n)); // 1764n diff --git a/examples/parser.js b/examples/parser.js index e3d5701..0c5ce7c 100644 --- a/examples/parser.js +++ b/examples/parser.js @@ -1,5 +1,7 @@ -import { parse } from "../parser/parser.js"; -import { prettyGenT, prettyT } from "../util/pretty.js"; +import { getDefaultTypeParser }from "../lib/parser/type_parser.js"; +import { prettyGenT, prettyT } from "../lib/util/pretty.js"; + +const parse = getDefaultTypeParser(); console.log(prettyT(parse("Int"))); // Int @@ -12,4 +14,3 @@ console.log(prettyT(parse("#0((Int * #0) + Unit)"))) // #0((Int ⨯ #0) + Unit) console.log(prettyGenT(parse("∀a: #0((a * #0) + Unit"))); // ∀a: #0((a ⨯ #0) + Unit) console.log(prettyGenT(parse("∀a,b,c,d: (a*b) + (c*d)"))); // ∀a,b,c,d: ((a ⨯ b) + (c ⨯ d)) - diff --git a/examples/prompt.js b/examples/prompt.js index f2cea2b..7372296 100644 --- a/examples/prompt.js +++ b/examples/prompt.js @@ -1,14 +1,15 @@ import { select, number, input } from '@inquirer/prompts'; import { ModulePoint } from "../lib/point.js"; -import { DefaultMap } from "../util/defaultmap.js"; -import { pretty } from '../util/pretty.js'; +import { DefaultMap } from "../lib/util/defaultmap.js"; +import { pretty } from '../lib/util/pretty.js'; import { isFunction } from '../structures/types.js'; -import { ModuleStd } from '../stdlib.js'; +import { ModuleStd } from '../lib/stdlib.js'; import { Double, GenericType, Int, SymbolT, Type } from "../primitives/types.js"; import { eqType } from '../primitives/type.js'; import { Top } from "../primitives/types.js"; -import { assignFn, makeGeneric, onlyOccurring } from '../generics/generics.js'; -import { prettyT } from '../util/pretty.js'; +import { assignFn, makeGeneric, onlyOccurring } from '../lib/generics/generics.js'; +import { prettyT } from '../lib/util/pretty.js'; +import { genUUID } from '../lib/util/random.js'; // import {emitKeypressEvents} from 'node:readline'; @@ -289,7 +290,7 @@ async function createInstance(t) { else if (eqType(t)(SymbolT)) { console.log("Note: you are creating a new Symbol. Even if the description matches that of another symbol (e.g., \"Int\"), a new Symbol will be created that is unique and only equal to itself."); const symbolDescr = await input({message: "enter symbol description:"}); - return Symbol(symbolDescr); + return symbolDescr + '__' + genUUID(16); } else { console.log("no prompt handler for creating new", prettyT(t)); diff --git a/examples/rbtree.js b/examples/rbtree.js new file mode 100644 index 0000000..1ad2369 --- /dev/null +++ b/examples/rbtree.js @@ -0,0 +1,13 @@ +import createRBTree from "functional-red-black-tree"; + +console.log("#############"); +console.log("## RB Tree ##"); +console.log("#############"); + +// just a small experiment +console.log( + createRBTree() + .insert(1) + .insert(1) + .insert(2) +); diff --git a/examples/recursive_types.js b/examples/recursive_types.js index 5431b8d..fae27f8 100644 --- a/examples/recursive_types.js +++ b/examples/recursive_types.js @@ -1,8 +1,10 @@ -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"; +import { compareTypes } from "../lib/compare/type.js"; +import { makeGeneric, substitute, unify } from "../lib/generics/generics.js"; +import { Double, Int, Unit } from "../lib/primitives/primitive_types.js"; +import { fnType, lsType, prodType, sumType, setType } from "../lib/structures/type_constructors.js"; +import { prettyGenT, prettyT } from "../lib/util/pretty.js"; + +Error.stackTraceLimit = Infinity; // some recursive types: diff --git a/examples/versioning.js b/examples/versioning.js index 70e63b8..f4688c0 100644 --- a/examples/versioning.js +++ b/examples/versioning.js @@ -1,7 +1,6 @@ import { pretty } from "../util/pretty.js"; import { newLiteral, transform, read, getReadDependencies, verifyValue } from "../versioning/value.js"; import { merge, merge2, newSlot, overwrite } from "../versioning/slot.js"; -import createRBTree from "functional-red-black-tree"; import { add, emptySet, RBTreeWrapper } from "../structures/set.js"; import { compareNumbers } from "../compare/primitives.js"; @@ -77,39 +76,20 @@ console.log(pretty({sixSevenEightSlot})); // console.log("########################"); // console.log("## Heterogeneous data ##"); // console.log("########################"); - // // Slot // const numberOfSheepSlot = newSlot(Symbol('numberOfSheep'))(newLiteral(5)); // const alternativeNumberOfSheepSlot = newSlot(Symbol('alternativeNumberOfSheep'))(newLiteral(6)); // // Slot // const labelSlot = newSlot(Symbol('label'))(newLiteral("number of sheep")); - // const combineFn = newLiteral(label => numberOfSheep => `${label}: ${numberOfSheep}`) - // // Slot // const labelAndValueSlotA = overwrite(labelSlot)( // transform(read(numberOfSheepSlot))( // transform(read(labelSlot))(combineFn))); - // const labelAndValueSlotB = overwrite(labelSlot)( // transform(read(alternativeNumberOfSheepSlot))( // transform(read(labelSlot))(combineFn))); - // console.log( // add(add(emptySet(compareSlots(compareStrings)))(labelAndValueSlotA))(labelAndValueSlotB) // ); - // merge()(labelSlot)(labelAndValueSlot) - - -console.log("#############") -console.log("## RB Tree ##") -console.log("#############") - -// just a small experiment -console.log( - createRBTree() - .insert(1) - .insert(1) - .insert(2) -); diff --git a/extra/point/nominal.js b/extra/point/nominal.js new file mode 100644 index 0000000..a888488 --- /dev/null +++ b/extra/point/nominal.js @@ -0,0 +1,23 @@ +export const cart2polar = ({x, y}) => { + const r = Math.sqrt(x*x + y*y); + const θ = Math.atan(y/x); + return {r, θ}; +}; + +export const polar2cart = ({r, θ}) => { + const x = r * Math.cos(θ); + const y = r * Math.sin(θ); + return {x, y}; +} + +export const translate = dx => dy => ({x, y}) => { + return {left: x+dx, right: y+dy}; +} + +export const rotate = dθ => ({r, θ}) => { + return {r, θ: θ+dθ}; +} + +export const scale = dr => ({r, θ}) => { + return {r: r+dr, θ}; +} diff --git a/extra/point/nominal.types.js b/extra/point/nominal.types.js new file mode 100644 index 0000000..06ff6dc --- /dev/null +++ b/extra/point/nominal.types.js @@ -0,0 +1,30 @@ +import { makeTypeParser } from "../../lib/parser/type_parser.js"; +import { Type } from "../../lib/primitives/primitive_types.js"; +import { makeTypeConstructor } from "../../lib/meta/type_constructor.js"; +import { cart2polar, polar2cart, rotate, scale, translate } from "./nominal.js"; + +const PointCartesian2D = makeTypeConstructor('PCartesian2D__20bb64ce2cd52cfc6702f7d5d9bd1e60')(0); +const PointPolar2D = makeTypeConstructor('PPolar2D__dd566869d57d440e0bc299c53cac3846')(0); + +const examplePoint = {x: 1, y: 2}; + +const mkType = makeTypeParser({ + extraPrimitives: [ + ["PointCartesian2D", PointCartesian2D], + ["PointPolar2D", PointPolar2D], + ] +}); + +export const ModulePointNominal = [ + {i: PointCartesian2D , t: Type}, + {i: PointPolar2D , t: Type}, + + {i: examplePoint , t: PointCartesian2D}, + + {i: cart2polar, t: mkType("PointCartesian2D -> PointPolar2D")}, + {i: polar2cart, t: mkType("PointPolar2D -> PointCartesian2D")}, + + {i: translate, t: mkType("Double -> Double -> PointCartesian2D")}, + {i: rotate , t: mkType("Double -> PointPolar2D -> PointPolar2D")}, + {i: scale , t: mkType("Double -> PointPolar2D -> PointPolar2D")}, +]; diff --git a/extra/point/structural.types.js b/extra/point/structural.types.js new file mode 100644 index 0000000..2904966 --- /dev/null +++ b/extra/point/structural.types.js @@ -0,0 +1,81 @@ +import { makeTypeParser } from "../../lib/parser/type_parser.js"; +import { makeModuleStruct } from "../../lib/structures/struct.types.js"; +import { makeTypeConstructor } from "../../lib/meta/type_constructor.js"; +import { newProduct } from "../../lib/structures/product.js"; +import { Double } from "../../lib/primitives/primitive_types.js"; + +// Nominal types: +const NPoint2DCartesian = makeTypeConstructor('PCartesian2D__7efe2dd14d9b036e2e83d6e4771c88ac')(0); +const NPoint2DPolar = makeTypeConstructor('PPolar2D__9297b15478804ee209a91f1af943b67a')(0); + +// Structural types: + +const ModuleCartesian = makeModuleStruct([ + newProduct("x")(Double), + newProduct("y")(Double), +]); +const ModulePolar = makeModuleStruct([ + newProduct("r")(Double), + newProduct("θ")(Double), +]); + +const [ + {i: SPoint2DCartesian}, + {i: newCartesian}, + {i: getX}, + {i: getY}, +] = ModuleCartesian; + +const [ + {i: SPoint2DPolar}, + {i: newPolar}, + {i: getR}, + {i: getΘ}, +] = ModulePolar; + +const cart2polar = cart => { + const x = getX(cart); + const y = getY(cart); + const r = Math.sqrt(x*x + y*y); + const θ = Math.atan(y/x); + return newPolar(r)(θ); +}; + +const polar2cart = polar => { + const r = getR(polar); + const θ = getΘ(polar); + const x = r * Math.cos(θ); + const y = r * Math.sin(θ); + return newCartesian(x)(y); +}; + +const mkType = makeTypeParser({ + extraPrimitives: [ + ["NPoint2DCartesian", NPoint2DCartesian], + ["NPoint2DPolar" , NPoint2DPolar ], + ], +}); + +const ModuleConversions = [ + { i: NPoint2DCartesian , t: mkType("NPoint2DCartesian") }, + { i: NPoint2DPolar , t: mkType("NPoint2DPolar") }, + { i: cart2polar , t: mkType("NPoint2DCartesian -> NPoint2DPolar") }, + { i: polar2cart , t: mkType("NPoint2DPolar -> NPoint2DCartesian") }, +]; + +const examplePointCart = newCartesian(1)(2); +const examplePointPolar = newPolar(0)(5); + +const ModuleExamples = [ + { i: examplePointCart , t: SPoint2DCartesian }, + { i: examplePointCart , t: NPoint2DCartesian }, + { i: examplePointPolar , t: SPoint2DPolar }, + { i: examplePointPolar , t: NPoint2DPolar }, +]; + +export const ModuleAll = [ + ...ModuleCartesian, + ...ModulePolar, + ...ModuleConversions, + ...ModuleExamples, +]; diff --git a/compare/primitives.js b/lib/compare/primitives.js similarity index 73% rename from compare/primitives.js rename to lib/compare/primitives.js index 5e15119..f043a1c 100644 --- a/compare/primitives.js +++ b/lib/compare/primitives.js @@ -22,8 +22,7 @@ export const compareBools = x => y => { }; // The Unit-type has only one instance, which is equal to itself: -export const compareUnits = x => y => 0; +export const compareUnits = _ => _ => 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 => Number(a !== b) && compareStrings(a.description)(b.description); +// Symbols are encoded as strings +export const compareSymbols = a => b => compareStrings(a)(b); diff --git a/lib/compare/primitives.types.js b/lib/compare/primitives.types.js new file mode 100644 index 0000000..c7a17fc --- /dev/null +++ b/lib/compare/primitives.types.js @@ -0,0 +1,11 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { compareBools, compareNumbers, compareSymbols, compareUnits } from "./primitives.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleComparePrimitives = [ + {i: compareNumbers, t: mkType("Double -> Double -> Int")}, + {i: compareBools , t: mkType("Bool -> Bool -> Int")}, + {i: compareUnits , t: mkType("Unit -> Unit -> Int")}, + {i: compareSymbols, t: mkType("SymbolT -> SymbolT -> Int")}, +]; diff --git a/lib/compare/registry.js b/lib/compare/registry.js new file mode 100644 index 0000000..bfda00f --- /dev/null +++ b/lib/compare/registry.js @@ -0,0 +1,35 @@ +import { getInst, getType } from "../primitives/dynamic.js"; +import { SymbolBool, SymbolChar, SymbolDouble, SymbolInt, SymbolType, SymbolUnit } from "../primitives/primitive_types.js"; +import { getHumanReadableName } from "../primitives/symbol.js"; +import { symbolDict, symbolList, symbolProduct, symbolSet, symbolSum } from "../structures/type_constructors.js"; +import { capitalizeFirstLetter } from "../util/util.js"; +import { compareBools, compareNumbers, compareStrings, compareUnits } from "./primitives.js"; +import { compareLists, compareProducts, compareSets, compareSums } from "./structures.js"; +import { compareTypes } from "./type.js"; + +const typeSymbolToCmp = new Map([ + [SymbolInt , compareNumbers], + [SymbolChar , compareStrings], + [SymbolDouble , compareNumbers], + [SymbolBool , compareBools], + [SymbolUnit , compareUnits], + [SymbolType , compareTypes], + + // these functions take extra comparison callbacks: + [symbolList , compareLists], + [symbolProduct , compareProducts], + [symbolSum , compareSums], + [symbolSet , compareSets], + // [symbolDict , compareDicts], TODO +]); + +export const makeCompareFn = type => { + return type.params.reduce( + (acc, cur) => acc(makeCompareFn(cur(type))), + typeSymbolToCmp.get(type.symbol) + ); +}; + +export const compareDynamic = x => y => + compareTypes(getType(x))(getType(y)) + || makeCompareFn(getType(x))(getInst(x))(getInst(y)); diff --git a/compare/structures.js b/lib/compare/structures.js similarity index 76% rename from compare/structures.js rename to lib/compare/structures.js index dbb9a9f..bde873a 100644 --- a/compare/structures.js +++ b/lib/compare/structures.js @@ -28,16 +28,22 @@ export const compareProducts = compareLeft => compareRight => x => y => { // (a -> a -> Int) -> (b -> b -> Int) -> (a | b) -> (a | b) -> Int export const compareSums = compareLeft => compareRight => x => y => { - return match(x)(newProduct - (leftValueX => match(y)(newProduct - (leftValueY => compareLeft(leftValueX)(leftValueY)) - ((rightValueY) => -1) // x is 'left' and y is 'right' => x < y - )) - (rightValueX => match(y)(newProduct - (leftValueY => 1) // x is 'right' and y is 'left' => x > y - (rightValueY => compareRight(rightValueX)(rightValueY)) - )) - ); + // console.log("compareSums...", x, y) + return match(x) + (leftValueX => match(y) + (leftValueY => compareLeft(leftValueX)(leftValueY)) // both are left + ((_rightValueY) => { + // console.log("x is 'left' and y is 'right' => x < y") + return -1; + }) // x is 'left' and y is 'right' => x < y + ) + (rightValueX => match(y) + (_leftValueY => { + // console.log("x is 'right' and y is 'left' => x > y"); + return 1; + }) // x is 'right' and y is 'left' => x > y + (rightValueY => compareRight(rightValueX)(rightValueY)) // both are right + ); }; // (a -> a -> Int) -> {a} -> {a} -> Int diff --git a/lib/compare/structures.types.js b/lib/compare/structures.types.js new file mode 100644 index 0000000..16c8fce --- /dev/null +++ b/lib/compare/structures.types.js @@ -0,0 +1,14 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { compareLists, compareProducts, compareSets, compareSums } from "./structures.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleCompareStructures = [ + {i: compareLists, t: mkType("∀a: (a -> a -> Int) -> [a] -> [a] -> Int")}, + + {i: compareProducts, t: mkType("∀a,b: (a -> a -> Int) -> (b -> b -> Int) -> (a*b) -> (a*b) -> Int")}, + + {i: compareSums, t: mkType("∀a,b: (a -> a -> Int) -> (b -> b -> Int) -> (a+b) -> (a+b) -> Int")}, + + {i: compareSets, t: mkType("∀a: (a -> a -> Int) -> {a} -> {a} -> Int")}, +]; \ No newline at end of file diff --git a/compare/type.js b/lib/compare/type.js similarity index 86% rename from compare/type.js rename to lib/compare/type.js index fb3a4dc..5f70d1c 100644 --- a/compare/type.js +++ b/lib/compare/type.js @@ -1,5 +1,5 @@ -import { getParams, getSymbol } from "../type_constructor.js"; -import { compareBools, compareSymbols } from "./primitives.js"; +import { getParams, getSymbol } from "../primitives/type.js"; +import { compareBools, compareNumbers, compareSymbols } from "./primitives.js"; import { compareLists } from "./structures.js"; const __compareTypes = state => typeX => typeY => { @@ -20,7 +20,7 @@ const __compareTypes = state => typeX => typeY => { // 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); + return compareNumbers(state.comparing.get(pX))(pY); } // none have been visited -> recursively compare return __compareTypes(state)(pX)(pY); diff --git a/lib/compare/type.types.js b/lib/compare/type.types.js new file mode 100644 index 0000000..ad11e3c --- /dev/null +++ b/lib/compare/type.types.js @@ -0,0 +1,8 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { compareTypes } from "./type.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleCompareTypes = [ + {i: compareTypes, t: mkType("Type -> Type -> Int")}, +]; diff --git a/compare/versioning.js b/lib/compare/versioning.js similarity index 100% rename from compare/versioning.js rename to lib/compare/versioning.js diff --git a/generics/generics.js b/lib/generics/generics.js similarity index 93% rename from generics/generics.js rename to lib/generics/generics.js index 1d8089a..d8ee7e4 100644 --- a/generics/generics.js +++ b/lib/generics/generics.js @@ -96,11 +96,11 @@ export const mergeTwoWay = (m1, m2) => { // 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}); + // console.log("__unify", {typeVars, fType: prettyT(fType), aType: prettyT(aType), fStack, aStack}); if (typeVars.has(fType)) { // simplest case: formalType is a type paramater // => substitute with actualType - // console.log("assign actual to formal"); + // console.log(`assign ${prettyT(aType)} to ${prettyT(fType)}`); return { substitutions: new Map([[fType, aType]]), genericType: { @@ -111,7 +111,7 @@ const __unify = (typeVars, fType, aType, fStack=[], aStack=[]) => { } if (typeVars.has(aType)) { // same as above, but in the other direction - // console.log("assign formal to actual"); + // console.log(`assign ${prettyT(fType)} to ${prettyT(aType)}`); return { substitutions: new Map([[aType, fType]]), genericType: { @@ -163,7 +163,7 @@ const __unify = (typeVars, fType, aType, fStack=[], aStack=[]) => { }; const [unifiedSubstitutions, unifiedTypeVars] = unifications.reduce((acc, getParam) => { - const self = Symbol(); + const self = Symbol(); // dirty, just need something unique const {substitutions, deletions} = mergeTwoWay(acc[0], getParam(self).substitutions); return [substitutions, acc[1] .difference(substitutions) @@ -206,8 +206,8 @@ export const assign = (genFnType, paramType) => { let allTypeVars; [allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType); const [inType, outType] = genFnType.type.params; - const {substitutions} = unifyInternal(allTypeVars, inType, paramType.type); - const substitutedOutType = substitute(outType, substitutions); + const {substitutions} = __unify(allTypeVars, inType(genFnType.type), paramType.type); + const substitutedOutType = substitute(outType(genFnType.type), substitutions); return recomputeTypeVars(onlyOccurring(substitutedOutType, allTypeVars)); }; @@ -215,7 +215,7 @@ export const assignFn = (genFnType, paramType) => { let allTypeVars; [allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType); const [inType] = genFnType.type.params; - const {substitutions} = unifyInternal(allTypeVars, inType, paramType.type); + const {substitutions} = __unify(allTypeVars, inType, paramType.type); const substitutedFnType = substitute(genFnType.type, substitutions); return recomputeTypeVars(onlyOccurring(substitutedFnType, allTypeVars)); }; diff --git a/lib/meta/type_constructor.js b/lib/meta/type_constructor.js new file mode 100644 index 0000000..825f76a --- /dev/null +++ b/lib/meta/type_constructor.js @@ -0,0 +1,21 @@ +import { getHumanReadableName } from "../primitives/symbol.js"; + +const __makeTypeConstructor = (symbol, nAry, params) => { + if (nAry === 0) { + return { symbol, params }; + } + // only for debugging, do we give the function a name + const fName = `${getHumanReadableName(symbol).toLowerCase()}Type${params.length>0?params.length:''}`; + return { + [fName]: typeParam => { + if (typeof typeParam !== 'function') { + throw new Error("all type params must be functions"); + } + return __makeTypeConstructor(symbol, nAry-1, params.concat([typeParam])); + } + }[fName]; +} + +// Creates a new nominal type +// export const makeTypeConstructor = symbol => nAry => makeTypeConstructorInternal(symbol, nAry); +export const makeTypeConstructor = symbol => nAry => __makeTypeConstructor(symbol, nAry, []); diff --git a/lib/meta/type_constructor.types.js b/lib/meta/type_constructor.types.js new file mode 100644 index 0000000..ff13e94 --- /dev/null +++ b/lib/meta/type_constructor.types.js @@ -0,0 +1,9 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js" +import { makeTypeConstructor } from "./type_constructor.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleTypeConstructor = [ + // Problem: number of parameters of returned function depends on the 'Int' parameter... + // {i: makeTypeConstructor, t: mkType("SymbolT -> Int -> ??")} +]; diff --git a/lib/parser/type_parser.js b/lib/parser/type_parser.js new file mode 100644 index 0000000..3ba9b07 --- /dev/null +++ b/lib/parser/type_parser.js @@ -0,0 +1,206 @@ +// A simple, hacked-together recursive parser for types. + +import { Bool, Char, Double, Int, SymbolT, Type, Unit } from "../primitives/primitive_types.js"; +import { Dynamic } from "../primitives/primitive_types.js"; +import { dictType, fnType, lsType, prodType, sumType } from "../structures/type_constructors.js"; +import { setType } from "../structures/type_constructors.js"; + +export const makeTypeParser = ({ + // parser can be extended: + extraPrimitives=[], + extraBracketOperators=[], + extraInfixOperators=[], +}) => { + const a = Symbol('a'); + const b = Symbol('b'); + const c = Symbol('c'); + const d = Symbol('d'); + const e = Symbol('e'); + + const primitives = new Map([ + ['Int', Int], + ['Double', Double], + ['Bool', Bool], + ['Char', Char], + ['String', lsType(_ => Char)], + ['Module', lsType(_ => Dynamic)], + ['Unit', Unit], + ['Type', Type], + ['Dynamic', Dynamic], + ['SymbolT', SymbolT], + ['a', a], + ['b', b], + ['c', c], + ['d', d], + ['e', e], + + ...extraPrimitives, + ]); + + const bracketOperators = new Map([ + ['(', [')', null]], + ['[', [']', lsType]], + ['{', ['}', setType]], + + // can only occur at beginning + // we use these to extract the type variables + ['∀', [':', null]], + + ...extraBracketOperators, + ]); + + const infixOperators = new Map([ + ['+', sumType], + ['|', sumType], + ['⨯', prodType], + ['*', prodType], + ['→', fnType], + ['->', fnType], + ['⇒', dictType], + ['=>', dictType], + + // only used for type variables (e.g., ∀a,b,c:) + [',', fnX => fnY => { + const x = fnX(); + const y = fnY(); + return Array.isArray(x) ? x.concat(y) : [x].concat(y) + }], + + ...extraInfixOperators, + ]); + + + const TOKENS = [ + ...bracketOperators.keys(), + ...[...bracketOperators.values()].map(v => v[0]), + ...infixOperators.keys(), + ...primitives.keys(), + ].toSorted((a,b) => { + return (b.length - a.length); // try longer tokens first + }); + + // console.log('TOKENS =', TOKENS); + + const tokenize = expr => { + const tokens = []; + let i=0; + outerloop: while (i { + const bracket = bracketOperators.get(tokens[0]); + if (bracket === undefined) { + // no group, just a single token: + const [firstToken, ...rest] = tokens; + return [[firstToken], null, rest]; + } + else { + // find where group ends: + const [closing, fn] = bracket; + const opening = tokens[0] + let depth = 1; + let i = 1; + for (; i { + // console.log('parseGroup ', tokensInGroup, fn); + return (fn === null) + ? __parse(tokensInGroup, labels, label) + : fn(self => { + return __parse(tokensInGroup, extendLabels(labels, label, self)); + }); + } + + const extendLabels = (labels, label, self) => { + return (label === null) ? labels : new Map([...labels, [label, self]]) + }; + + const __parse = (tokens, labels = new Map(), label = null) => { + // console.log('parse ', tokens); + if (tokens[0].startsWith('#')) { + if (labels.has(tokens[0])) { + return labels.get(tokens[0]); + } + else { + // pass label and parse 'rest' + return __parse(tokens.slice(1), labels, tokens[0]); + } + } + if (tokens.length === 1) { + return primitives.get(tokens[0]); + } + else { + const [lhsTokens, fnGrp, rest] = consumeGroup(tokens); + if (rest.length === 0) { + return parseGroup(lhsTokens, fnGrp, labels, label); + } + const [operator, ...rhsTokens] = rest; + for (const [operatorChar, fn] of infixOperators) { + if (operator === operatorChar) { + return fn + (self => { + return parseGroup(lhsTokens, fnGrp, extendLabels(labels, label, self)); + })(self => { + return __parse(rhsTokens, extendLabels(labels, label, self)); + }); + } + } + throw new Error("unknown operator: "+operator) + } + }; + + const parse = expr => { + const tokens = tokenize(expr); + if (tokens[0] === '∀') { + // generic type + const [typeVarTokens, _, rest] = consumeGroup(tokens); + const typeVars = [].concat(__parse(typeVarTokens)) + const type = __parse(rest); + return { typeVars, type }; + } + return __parse(tokens); + } + + return parse; +} + +let defaultParser; +export const getDefaultTypeParser = () => { + return defaultParser || (defaultParser = makeTypeParser({})); +}; diff --git a/lib/point.js b/lib/point.js deleted file mode 100644 index cb116cb..0000000 --- a/lib/point.js +++ /dev/null @@ -1,73 +0,0 @@ -import { Type } from "../primitives/types.js"; -import { typedFnType } from "../structures/types.js" -import { Double } from "../primitives/types.js"; -import { makeTypeConstructor } from "../type_constructor.js"; - -// Create nominal types -export const PointCartesian2D = makeTypeConstructor(Symbol('PointCartesian2D'))(0); -export const PointPolar2D = makeTypeConstructor(Symbol('PointPolar2D'))(0); - -export const cart2polar = ({x, y}) => { - const r = Math.sqrt(x*x + y*y); - const θ = Math.atan(y/x); - return {r, θ}; -}; - -export const polar2cart = ({r, θ}) => { - const x = r * Math.cos(θ); - const y = r * Math.sin(θ); - return {x, y}; -} - -export const translate = dx => dy => ({x, y}) => { - return {left: x+dx, right: y+dy}; -} - -export const rotate = dθ => ({r, θ}) => { - return {r, θ: θ+dθ}; -} - -export const scale = dr => ({r, θ}) => { - return {r: r+dr, θ}; -} - -const examplePoint = {x: 1, y: 2}; - -export const ModulePoint = {l:[ - {i: -1 , t: Double}, - {i: 2 , t: Double}, - {i: examplePoint , t: PointCartesian2D}, - - {i: PointCartesian2D , t: Type}, - {i: PointPolar2D , t: Type}, - - ...typedFnType(cart2polar, fnType => fnType(() => PointCartesian2D)(() => PointPolar2D)), - - ...typedFnType(polar2cart, fnType => fnType(() => PointPolar2D)(() => PointCartesian2D)), - - // Double -> Double -> PointCartesian2D -> PointCartesian2D - ...typedFnType(translate, fnType => - fnType - (Double) - (fnType - (Double) - (fnType - (PointCartesian2D) - (PointCartesian2D)))), - - // Double -> PointPolar2D -> PointPolar2D - ...typedFnType(rotate, fnType => - fnType - (Double) - (fnType - (PointPolar2D) - (PointPolar2D))), - - // Double -> PointPolar2D -> PointPolar2D - ...typedFnType(scale, fnType => - fnType - (Double) - (fnType - (PointPolar2D) - (PointPolar2D))), -]}; diff --git a/lib/point_structural.js b/lib/point_structural.js deleted file mode 100644 index d530c82..0000000 --- a/lib/point_structural.js +++ /dev/null @@ -1,140 +0,0 @@ -import { prettyT, typedFnType } from "../structures/types.js" -import { Double } from "../primitives/types.js"; -import { makeConstructor, makeConstructorType, makeGetters, makeGettersTypes, structType } from "../structures/struct.js"; -import { newProduct } from "../structures/product.js"; -import { makeTypeConstructor } from "../type_constructor.js"; - -const cartFields = [ - newProduct("x")(Double), - newProduct("y")(Double), -]; - -const polarFields = [ - newProduct("r")(Double), - newProduct("θ")(Double), -]; - -// Nominal types: -export const NPoint2DCartesian = makeTypeConstructor(Symbol('Point2DCartesian'))(0); -export const NPoint2DPolar = makeTypeConstructor(Symbol('Point2DPolar'))(0); - -// Structural types: -export const SPoint2DCartesian = structType(cartFields); // (Double, (Double, Unit)) -export const SPoint2DPolar = structType(polarFields); // (Double, (Double, Unit)) - -export const constructorPoint2DCartesian = makeConstructor(cartFields); -export const constructorPoint2DPolar = makeConstructor(polarFields); - -export const constructorPoint2DCartesianFnType = makeConstructorType(NPoint2DCartesian)(cartFields); -export const constructorPoint2DPolarFnType = makeConstructorType(NPoint2DPolar)(polarFields); - -export const [getX, getY] = makeGetters(cartFields); -export const [getR, getΘ] = makeGetters(polarFields); - -export const [getXFnType, getYFnType] = makeGettersTypes(NPoint2DCartesian)(cartFields); -export const [getRFnType, getΘFnType] = makeGettersTypes(NPoint2DPolar)(polarFields); - -export const cart2polar = cart => { - const x = getX(cart); - const y = getY(cart); - const r = Math.sqrt(x*x + y*y); - const θ = Math.atan(y/x); - return constructorPoint2DPolar(r)(θ); -}; - -export const polar2cart = polar => { - const r = getR(polar); - const θ = getΘ(polar); - const x = r * Math.cos(θ); - const y = r * Math.sin(θ); - return constructorPoint2DCartesian(x)(y); -}; - -export const cart2Str = cart => { - const x = getX(cart); - const y = getY(cart); - return {l: `{x: ${x}, y: ${y}}`}; -}; - -export const polar2Str = polar => { - const r = getR(polar); - const θ = getΘ(polar); - return {l: `{r: ${r}, θ: ${θ}}`}; -}; - -// export const translate = dx => dy => ({x, y}) => { -// return {x: x+dx, y: y+dy}; -// }; - -// export const rotate = dθ => ({r, θ}) => { -// return {r, θ: θ+dθ}; -// }; - -// export const scale = dr => ({r, θ}) => { -// return {r: r+dr, θ}; -// }; - -// const examplePoint = {x: 1, y: 2}; - -const examplePointCart = constructorPoint2DCartesian(1)(2); -const examplePointPolar = constructorPoint2DCartesian(0)(5); - -console.log(prettyT(getXFnType)); - -export const ModulePoint = {l:[ - // {i: -1 , t: Double}, - // {i: 2 , t: Double}, - // {i: examplePoint , t: Point2DCartesian}, - - {i: examplePointCart, t: SPoint2DCartesian}, - {i: examplePointCart, t: NPoint2DCartesian}, - - {i: examplePointPolar, t: SPoint2DPolar}, - {i: examplePointPolar, t: NPoint2DPolar}, - - // {i: SPoint2DCartesian , t: Type}, - // {i: SPoint2DPolar , t: Type}, - - // {i: NPoint2DCartesian , t: Type}, - // {i: NPoint2DPolar , t: Type}, - - {i: getX, t: getXFnType}, - {i: getY, t: getYFnType}, - {i: getR, t: getRFnType}, - {i: getΘ, t: getΘFnType}, - - ...typedFnType(cart2polar, fnType => fnType(() => NPoint2DCartesian)(() => NPoint2DPolar)), - ...typedFnType(polar2cart, fnType => fnType(() => NPoint2DPolar)(() => NPoint2DCartesian)), - - // ...typedFnType(polar2cart, fnType => fnType(() => Point2DPolar)(() => Point2DCartesian)), - - // // Double -> Double -> PointCartesian2D -> PointCartesian2D - // ...typedFnType(translate, fnType => - // fnType - // (Double) - // (fnType - // (Double) - // (fnType - // (Point2DCartesian) - // (Point2DCartesian)))), - - // ...typedFnType(cart2tuple, fnType => fnType(() => Point2DCartesian)(prodType(() => Double)(() => () => Double))), - - // ...typedFnType(polar2tuple, fnType => fnType(() => Point2DPolar)(prodType(() => Double)(() => () => Double))), - - // // Double -> PointPolar2D -> PointPolar2D - // ...typedFnType(rotate, fnType => - // fnType - // (Double) - // (fnType - // (Point2DPolar) - // (Point2DPolar))), - - // // Double -> PointPolar2D -> PointPolar2D - // ...typedFnType(scale, fnType => - // fnType - // (Double) - // (fnType - // (Point2DPolar) - // (Point2DPolar))), -]}; diff --git a/lib/primitives/double.js b/lib/primitives/double.js new file mode 100644 index 0000000..281e815 --- /dev/null +++ b/lib/primitives/double.js @@ -0,0 +1,3 @@ +export const addDouble = x => y => x + y; +export const mulDouble = x => y => x * y; +export const eqDouble = x => y => x === y; diff --git a/lib/primitives/double.types.js b/lib/primitives/double.types.js new file mode 100644 index 0000000..6e64769 --- /dev/null +++ b/lib/primitives/double.types.js @@ -0,0 +1,10 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { addDouble, eqDouble, mulDouble } from "./double.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleDouble = [ + { i: addDouble, t: mkType("Double -> Double -> Double") }, + { i: mulDouble, t: mkType("Double -> Double -> Double") }, + { i: eqDouble, t: mkType("Double -> Double -> Bool") }, +]; diff --git a/lib/primitives/dynamic.js b/lib/primitives/dynamic.js new file mode 100644 index 0000000..6f01125 --- /dev/null +++ b/lib/primitives/dynamic.js @@ -0,0 +1,3 @@ +export const newDynamic = i => t => ({i, t}); +export const getInst = lnk => lnk.i; +export const getType = lnk => lnk.t; diff --git a/lib/primitives/dynamic.types.js b/lib/primitives/dynamic.types.js new file mode 100644 index 0000000..1cf645a --- /dev/null +++ b/lib/primitives/dynamic.types.js @@ -0,0 +1,14 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { getInst, getType, newDynamic } from "./dynamic.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleDynamic = [ + { i: newDynamic, t: mkType("∀a: a -> Type -> Dynamic")}, + + // allows us to (unsafely) cast the result to the specific type... + // (not sure if this is the right way to go) + { i: getInst, t: mkType("∀a: Dynamic -> a") }, + + { i: getType, t: mkType("Dynamic -> Type") }, +]; diff --git a/lib/primitives/int.js b/lib/primitives/int.js new file mode 100644 index 0000000..ef9e19d --- /dev/null +++ b/lib/primitives/int.js @@ -0,0 +1,3 @@ +export const addInt = x => y => x + y; +export const mulInt = x => y => x * y; +export const eqInt = x => y => x === y; diff --git a/lib/primitives/int.types.js b/lib/primitives/int.types.js new file mode 100644 index 0000000..dd3a2b8 --- /dev/null +++ b/lib/primitives/int.types.js @@ -0,0 +1,10 @@ +import { getDefaultTypeParser }from "../parser/type_parser.js"; +import { addInt, eqInt, mulInt } from "./int.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleInt = [ + { i: addInt, t: mkType("Int -> Int -> Int") }, + { i: mulInt, t: mkType("Int -> Int -> Int") }, + { i: eqInt, t: mkType("Int -> Int -> Bool") }, +]; diff --git a/lib/primitives/primitive_types.js b/lib/primitives/primitive_types.js new file mode 100644 index 0000000..c4b7f88 --- /dev/null +++ b/lib/primitives/primitive_types.js @@ -0,0 +1,40 @@ +// to break up dependency cycles, primitive types are defined in their own JS module + +import { makeTypeConstructor } from "../meta/type_constructor.js"; + +export const SymbolInt = "Int__02a884563f7d480bb14c09be640dfe7a"; +export const SymbolBool = "Bool__d64c4865bead40439dad62727aaaac2d"; +export const SymbolDouble = "Double__be70f3c8f53f4419a7866d106faae091"; +export const SymbolByte = "Byte__bf9e8453cd554e81971880ba33dc9f27"; +export const SymbolChar = "Char__e47159519d3345119336b751fc8da1de"; +export const SymbolUnit = "Unit__a70ca021c32a4036a594d332aedfb029"; +export const SymbolBottom = "⊥__95beece951bc457781be8c5481d35dcc"; +export const SymbolSymbol = "Symbol__f67c077430e04e4fa40ed2e2b2a3040d"; +export const SymbolType = "Type__fdbea309d66f49b483b0dd4ceb785f7d"; +export const SymbolTop = "⊤__38709c3c0039468782103256d4730d1f"; +export const SymbolGenericType = "GenericType__e9d8010b82f64206afa83d0c163df5d2"; +export const SymbolDynamic = "Dynamic__3c16c415dba94228ada37dc9d446f54f"; + +export const Int = makeTypeConstructor(SymbolInt)(0); +export const Bool = makeTypeConstructor(SymbolBool)(0); +export const Double = makeTypeConstructor(SymbolDouble)(0); +export const Byte = makeTypeConstructor(SymbolByte)(0); +export const Char = makeTypeConstructor(SymbolChar)(0); + +// Unit type has only 1 instance, the empty tuple. +export const Unit = makeTypeConstructor(SymbolUnit)(0); + +// Bottom type has no instances. +export const Bottom = makeTypeConstructor(SymbolBottom)(0); + +export const SymbolT = makeTypeConstructor(SymbolSymbol)(0); + +// Types are typed by Top +export const Type = makeTypeConstructor(SymbolType)(0); + +export const GenericType = makeTypeConstructor(SymbolGenericType)(0); + +// Everything is typed by Top +export const Top = makeTypeConstructor(SymbolTop)(0);// A type-link, connecting a value to its Type. + +export const Dynamic = makeTypeConstructor(SymbolDynamic)(0); diff --git a/lib/primitives/primitive_types.types.js b/lib/primitives/primitive_types.types.js new file mode 100644 index 0000000..c5a49d1 --- /dev/null +++ b/lib/primitives/primitive_types.types.js @@ -0,0 +1,31 @@ +import { SymbolInt, SymbolT, SymbolBool, SymbolDouble, SymbolByte, SymbolChar, SymbolUnit, SymbolBottom, SymbolSymbol, SymbolType, SymbolGenericType, SymbolTop, Type, Int, Bool, Double, Byte, Char, Unit, Bottom, GenericType, Top, SymbolDynamic, Dynamic } from "./primitive_types.js"; + +export const ModulePrimitiveSymbols = [ + { 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 }, + { i: SymbolDynamic , t: SymbolT }, +]; + +export const ModulePrimitiveTypes = [ + { i: Int , t: Type }, + { i: Bool , t: Type }, + { i: Double , t: Type }, + { i: Byte , t: Type }, + { i: Char , t: Type }, + { i: Unit , t: Type }, + { i: Bottom , t: Type }, + { i: SymbolT , t: Type }, + { i: Type , t: Type }, + { i: GenericType , t: Type }, + { i: Top , t: Type }, + { i: Dynamic , t: Type }, +]; diff --git a/lib/primitives/symbol.js b/lib/primitives/symbol.js new file mode 100644 index 0000000..06444a8 --- /dev/null +++ b/lib/primitives/symbol.js @@ -0,0 +1,2 @@ +export const getHumanReadableName = symbol => symbol.slice(0, -34); // drop UUID +export const eqSymbol = a => b => a === b; diff --git a/lib/primitives/symbol.types.js b/lib/primitives/symbol.types.js new file mode 100644 index 0000000..04f74a3 --- /dev/null +++ b/lib/primitives/symbol.types.js @@ -0,0 +1,9 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js" +import { eqSymbol, getHumanReadableName } from "./symbol.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleSymbol = [ + { i: getHumanReadableName, t: mkType("SymbolT -> String")}, + { i: eqSymbol, t: mkType("SymbolT -> SymbolT -> Bool")}, +]; diff --git a/lib/primitives/type.js b/lib/primitives/type.js new file mode 100644 index 0000000..ad30b9d --- /dev/null +++ b/lib/primitives/type.js @@ -0,0 +1,6 @@ +import { compareTypes } from "../compare/type.js"; + +export const getSymbol = type => type.symbol; +export const getParams = type => type.params; + +export const eqType = t1 => t2 => compareTypes(t1, t2) === 0; diff --git a/lib/primitives/type.types.js b/lib/primitives/type.types.js new file mode 100644 index 0000000..52992a0 --- /dev/null +++ b/lib/primitives/type.types.js @@ -0,0 +1,13 @@ +// a module is just a set of typed objects + +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { eqType, getParams, getSymbol } from "./type.js"; + +const mkType = getDefaultTypeParser(); + +// each 'typed object' is implicitly an instance of TypeLink (defined below) +export const ModuleType = [ + {i: eqType , t: mkType("Type -> Type -> Bool")}, + {i: getSymbol, t: mkType("Type -> SymbolT")}, + {i: getParams, t: mkType("Type -> [Type -> Type]")}, +]; diff --git a/lib/primitives/unit.js b/lib/primitives/unit.js new file mode 100644 index 0000000..b4634e7 --- /dev/null +++ b/lib/primitives/unit.js @@ -0,0 +1,3 @@ +export const unit = {}; + +export const eqUnit = _ => _ => true; diff --git a/lib/primitives/unit.types.js b/lib/primitives/unit.types.js new file mode 100644 index 0000000..33f64e5 --- /dev/null +++ b/lib/primitives/unit.types.js @@ -0,0 +1,9 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { eqUnit, unit } from "./unit.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleUnit = [ + {i: unit , t: mkType("Unit")}, + {i: eqUnit, t: mkType("Unit -> Unit -> Bool")}, +]; diff --git a/lib/stdlib.js b/lib/stdlib.js new file mode 100644 index 0000000..8c85332 --- /dev/null +++ b/lib/stdlib.js @@ -0,0 +1,48 @@ +import { ModuleDouble } from "./primitives/double.types.js"; +import { ModuleDynamic } from "./primitives/dynamic.types.js"; +import { ModuleInt } from "./primitives/int.types.js"; +import { ModulePrimitiveSymbols, ModulePrimitiveTypes } from "./primitives/primitive_types.types.js"; +import { ModuleSymbol } from "./primitives/symbol.types.js"; +import { ModuleType } from "./primitives/type.types.js"; +import { ModuleUnit } from "./primitives/unit.types.js"; + +import { ModuleDict } from "./structures/dict.types.js" +import { ModuleList } from "./structures/list.types.js" +import { ModuleProduct } from "./structures/product.types.js" +import { ModuleSet } from "./structures/set.types.js" +import { ModuleSum } from "./structures/sum.types.js" +import { ModuleStructuralSymbols, ModuleTypeConstructors } from "./structures/type_constructors.types.js"; + +import { ModuleCompareTypes } from "./compare/type.types.js"; +import { ModuleComparePrimitives } from "./compare/primitives.types.js"; +import { ModuleCompareStructures } from "./compare/structures.types.js"; + +export const ModuleStd = [ + // Symbols (for nominal types) + ...ModulePrimitiveSymbols, + ...ModuleStructuralSymbols, + + // Nominal types + ...ModulePrimitiveTypes, + ...ModuleTypeConstructors, + + // Operations on Primitives + ...ModuleDouble, + ...ModuleDynamic, + ...ModuleInt, + ...ModuleSymbol, + ...ModuleType, + ...ModuleUnit, + + // Operations on Structures + ...ModuleDict, + ...ModuleList, + ...ModuleProduct, + ...ModuleSet, + ...ModuleSum, + + // Comparison + ...ModuleCompareTypes, + ...ModuleComparePrimitives, + ...ModuleCompareStructures, +]; diff --git a/lib/structures/dict.js b/lib/structures/dict.js new file mode 100644 index 0000000..a5cc1cc --- /dev/null +++ b/lib/structures/dict.js @@ -0,0 +1,22 @@ +import { RBTreeWrapper } from "../util/rbtree_wrapper.js"; +import { newProduct } from "./product.js"; +import { newLeft, newRight } from "./sum.js"; + +export const emptyDict = compareFn => RBTreeWrapper.new((x, y) => compareFn(x)(y)); + +export const has = dict => key => dict.tree.get(key) === true; +export const set = dict => key => value => new RBTreeWrapper(dict.tree.remove(key).insert(key, value)); +export const remove = dict => key => new RBTreeWrapper(dict.tree.remove(key)); +export const length = dict => dict.tree.length; + +export const first = dict => dict.tree.begin; +export const last = dict => dict.tree.end; + +export const read = iter => { + if (iter !== undefined && iter.valid) { + return newRight(newProduct(newProduct(iter.key)(iter.value))(iter.clone().next())); + } + else { + return newLeft(unit); + } +}; diff --git a/lib/structures/dict.types.js b/lib/structures/dict.types.js new file mode 100644 index 0000000..3b2e7bc --- /dev/null +++ b/lib/structures/dict.types.js @@ -0,0 +1,20 @@ +import { makeTypeParser } from "../parser/type_parser.js"; +import { makeTypeConstructor } from "../meta/type_constructor.js"; +import { emptyDict, first, has, last, length, read, remove, set } from "./dict.js"; + +const dictIterator = makeTypeConstructor('DictIterator__d9d175b6bfd1283f00851a99787d0499')(2); + +const mkType = makeTypeParser({ + extraInfixOperators: [['|=>|', dictIterator]], +}); + +export const ModuleDict = [ + { i: emptyDict , t: mkType("∀a,b: (a -> a -> Int) -> (a => b)") }, + { i: has , t: mkType("∀a,b: (a => b) -> a -> Bool")}, + { i: set , t: mkType("∀a,b: (a => b) -> a -> b -> (a => b)")}, + { i: remove , t: mkType("∀a,b: (a => b) -> a -> (a => b)")}, + { i: length , t: mkType("∀a,b: (a => b) -> Int")}, + { i: first , t: mkType("∀a,b: (a => b) -> (a |=>| b)")}, + { i: last , t: mkType("∀a,b: (a => b) -> (a |=>| b)")}, + { i: read , t: mkType("∀a,b: (a |=>| b) -> (Unit + ((a*b) * (a |=>| b)))")}, +]; diff --git a/structures/enum.js b/lib/structures/enum.js similarity index 57% rename from structures/enum.js rename to lib/structures/enum.js index 40f1ca7..cfc6e3c 100644 --- a/structures/enum.js +++ b/lib/structures/enum.js @@ -1,24 +1,6 @@ -import { Bottom } from "../primitives/types.js"; import { capitalizeFirstLetter } from "../util/util.js"; -import { newProduct as newProduct, getLeft, getRight } from "./product.js"; +import { newProduct as newProduct, getLeft } from "./product.js"; import { newLeft, newRight, match } from "./sum.js"; -import { sumType } from "./types.js"; - -// 'variants' is an array of (name: string, type: Type) pairs. -// e.g., the list of variants: -// [ { l: "price" , r: Int }, -// { l: "prices" , r: [Int] }, -// { l: "not_found", r: Unit } ] -// results in the type: -// (Int | ([Int] | (Unit | ⊥))) -export const enumType = variants => { - if (variants.length === 0) { - return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated) - } - const [variant, ...rest] = variants; - const variantType = getRight(variant); - return sumType(() => variantType)(() => enumType(rest)); -}; const eatParameters = (numParams, result) => { if (numParams === 0) { @@ -33,11 +15,9 @@ export const makeMatchFn = variants => { } const [_, ...remainingVariants] = variants; return sum => handler => { - return ( - match(sum)(newProduct + return match(sum) (leftValue => eatParameters(remainingVariants.length, handler(leftValue))) - (rightValue => makeMatchFn(remainingVariants)(rightValue)) - )); + (rightValue => makeMatchFn(remainingVariants)(rightValue)); }; }; diff --git a/lib/structures/enum.types.js b/lib/structures/enum.types.js new file mode 100644 index 0000000..21436d9 --- /dev/null +++ b/lib/structures/enum.types.js @@ -0,0 +1,20 @@ +import { Bottom } from "../primitives/primitive_types.js"; +import { getRight } from "./product.js"; +import { sumType } from "./type_constructors.js"; + +// 'variants' is an array of (name: string, type: Type) pairs. +// e.g., the list of variants: +// [ { l: "price" , r: Int }, +// { l: "prices" , r: [Int] }, +// { l: "not_found", r: Unit } ] +// results in the type: +// (Int | ([Int] | (Unit | ⊥))) + +export const enumType = variants => { + if (variants.length === 0) { + return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated) + } + const [variant, ...rest] = variants; + const variantType = getRight(variant); + return sumType(() => variantType)(() => enumType(rest)); +}; diff --git a/lib/structures/list.js b/lib/structures/list.js new file mode 100644 index 0000000..884a6da --- /dev/null +++ b/lib/structures/list.js @@ -0,0 +1,16 @@ +// 'normal' implementation +export const emptyList = []; +// const emptyListType = makeGeneric(a => lsType(() => a)); +export const get = ls => i => ls[i]; +export const put = ls => i => elem => ls.with(Number(i), elem); +export const push = ls => elem => ls.concat([elem]); +export const pop = ls => ls.pop(); +export const map = ls => fn => ls.map(elem => fn(elem)); +export const length = ls => ls.length; +export const fold = ls => callback => initial => { + let acc = initial; + for (let i=0; i Int -> a")}, + { i: put , t: mkType("∀a: [a] -> Int -> a -> [a]")}, + { i: push , t: mkType("∀a: [a] -> a -> [a]")}, + { i: pop , t: mkType("∀a: [a] -> a")}, + { i: map , t: mkType("∀a: [a] -> (a -> b) -> [b]")}, + { i: length , t: mkType("∀a: [a] -> Int")}, + { i: fold , t: mkType("∀a: [a] -> (b -> a -> b) -> b -> b")}, +]; diff --git a/structures/product.js b/lib/structures/product.js similarity index 99% rename from structures/product.js rename to lib/structures/product.js index 64548db..e39c444 100644 --- a/structures/product.js +++ b/lib/structures/product.js @@ -3,7 +3,6 @@ // A Product-type always has only two fields, called "left" and "right". // Product-types of more fields (called Structs) can be constructed by nesting Product-types. - // In JS, all products are encoded in the same way: export const newProduct = l => r => ({l, r}); export const getLeft = product => product.l; diff --git a/lib/structures/product.types.js b/lib/structures/product.types.js new file mode 100644 index 0000000..47b3c13 --- /dev/null +++ b/lib/structures/product.types.js @@ -0,0 +1,10 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { newProduct, getLeft, getRight } from "./product.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleProduct = [ + { i: newProduct, t: mkType("∀a,b: a -> b -> (a * b)") }, + { i: getLeft , t: mkType("∀a,b: (a * b) -> a" ) }, + { i: getRight , t: mkType("∀a,b: (a * b) -> b" ) }, +]; diff --git a/lib/structures/set.js b/lib/structures/set.js new file mode 100644 index 0000000..e2257f0 --- /dev/null +++ b/lib/structures/set.js @@ -0,0 +1,29 @@ +import { newRight } from "./sum.js"; +import { newProduct } from "./product.js"; +import { unit } from "../primitives/unit.js"; +import { RBTreeWrapper } from "../util/rbtree_wrapper.js"; + +// (a -> a -> Int) -> Set(a) +export const emptySet = compareFn => RBTreeWrapper.new((x, y) => compareFn(x)(y)); + +export const has = set => key => set.tree.get(key) === true; +export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWrapper(set.tree.insert(key, true)); +export const remove = set => key => new RBTreeWrapper(set.tree.remove(key)); +export const length = set => set.tree.length; + +export const first = set => set.tree.begin; +export const last = set => set.tree.end; + +// test if iterator is 'done', and if not, get element and advance iterator. +export const read = iter => { + if (iter !== undefined && iter.valid) { + return newRight(newProduct(iter.key)(iter.clone().next())); + } + else { + return newLeft(unit); + } +}; + +export const forEach = set => fn => { + set.tree.forEach(key => { fn(key); }); +}; diff --git a/lib/structures/set.types.js b/lib/structures/set.types.js new file mode 100644 index 0000000..20e39da --- /dev/null +++ b/lib/structures/set.types.js @@ -0,0 +1,20 @@ +import { makeTypeParser } from "../parser/type_parser.js"; +import { makeTypeConstructor } from "../meta/type_constructor.js"; +import { emptySet, has, add, remove, length, first, read, last } from "./set.js"; + +const setIterator = makeTypeConstructor('SetIterator__f6b0ddd78ed41c58e5a442f2681da011')(1); + +const mkType = makeTypeParser({ + extraBracketOperators: [['<', ['>', setIterator]]], +}); + +export const ModuleSet = [ + { i: emptySet , t: mkType("∀a: (a -> a -> Int) -> {a}") }, + { i: has , t: mkType("∀a: {a} -> a -> Bool")}, + { i: add , t: mkType("∀a: {a} -> a -> {a}")}, + { i: remove , t: mkType("∀a: {a} -> a -> {a}")}, + { i: length , t: mkType("∀a: {a} -> Int")}, + { i: first , t: mkType("∀a: {a} -> ")}, + { i: last , t: mkType("∀a: {a} -> ")}, + { i: read , t: mkType("∀a: -> (Unit + (a * ))")}, +]; diff --git a/lib/structures/struct.js b/lib/structures/struct.js new file mode 100644 index 0000000..51c9d33 --- /dev/null +++ b/lib/structures/struct.js @@ -0,0 +1,33 @@ +import { unit } from "../primitives/unit.js"; +import { capitalizeFirstLetter } from "../util/util.js"; +import { newProduct, getLeft, getRight } from "./product.js"; + +export const makeConstructor = nParams => { + const internal = (nParams, ret) => { + if (nParams === 0) { + const result = ret(unit); + return result; + } + return nextParam => { + const wrappedName = 'wrapped_' + ret.name; + const newRet = { + [wrappedName]: inner => newProduct(nextParam)(ret(inner)), + }[wrappedName]; + return internal(nParams-1, newRet); + } + }; + const id = x => x; + return internal(nParams, id); +}; + +export const makeGetters = fieldNames => { + if (fieldNames.length === 0) { + return []; + } + const [fieldName, ...rest] = fieldNames; + const getterName = `get${capitalizeFirstLetter(fieldName)}`; + return [ + { [getterName]: obj => getLeft(obj) }[getterName], + ...makeGetters(rest).map(getter => ({[getter.name]: obj => getter(getRight(obj))}[getter.name])), + ]; +}; diff --git a/lib/structures/struct.types.js b/lib/structures/struct.types.js new file mode 100644 index 0000000..2946f65 --- /dev/null +++ b/lib/structures/struct.types.js @@ -0,0 +1,61 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { newDynamic } from "../primitives/dynamic.js"; +import { Type, Unit } from "../primitives/primitive_types.js"; +import { zip } from "../util/util.js"; +import { map } from "./list.js"; +import { getLeft, getRight } from "./product.js"; +import { makeConstructor, makeGetters } from "./struct.js"; +import { fnType, prodType } from "./type_constructors.js"; + + +// 'fields' is an array of (name: string, type: Type) pairs. +// e.g.: +// [{l: "x", r: Double}, {l: "y", r: Double}] +// results in the type (Double × (Double × Unit)) + +export const structType = fieldTypes => { + if (fieldTypes.length === 0) { + return Unit; + } + const [fieldType, ...rest] = fieldTypes; + return prodType(_ => fieldType)(_ => structType(rest)); +}; + +export const makeConstructorType = fieldTypes => { + if (fieldTypes.length === 0) { + return structType(fieldTypes); + } + const [fieldType, ...rest] = fieldTypes; + return fnType(_ => fieldType)(_ => makeConstructorType(rest)); +}; + +export const makeGettersTypes = fieldTypes => { + const type = structType(fieldTypes); + return fieldTypes.map(fieldType => { + return fnType(_ => type)(_ => fieldType); + }); +}; + +export const makeModuleStruct = fields => { + const fieldNames = map(fields)(getLeft); + const fieldTypes = map(fields)(getRight); + const type = structType(fieldTypes); + const ctor = makeConstructor(fields.length); + const ctorType = makeConstructorType(fieldTypes); + const getterTypes = makeGettersTypes(fieldTypes); + const getters = makeGetters(fieldNames); + const module = [ + {i: type, t: Type}, + {i: ctor, t: ctorType}, + ...zip(getters, getterTypes) + .map(([getter, getterType]) => newDynamic(getter)(getterType)), + ]; + return module; +}; + +const mkType = getDefaultTypeParser(); + +export const ModuleStruct = [ + {i: structType, t: mkType("[String*Type] -> Type")}, + {i: makeModuleStruct, t: mkType("[String*Type] -> [Dynamic]")}, +]; diff --git a/lib/structures/sum.js b/lib/structures/sum.js new file mode 100644 index 0000000..7d5b76d --- /dev/null +++ b/lib/structures/sum.js @@ -0,0 +1,11 @@ +// Sum-type (also called: tagged union, disjoint union, variant type) +// A Sum-type always has only two variants, called "left" and "right". +// Sum-types of more variants (called Enums) can be constructed by nesting Sum-types. + +export const newLeft = left => ({t: "L", v: left }); +export const newRight = right => ({t: "R", v: right}); + +export const match = sum => leftHandler => rightHandler => + sum.t === "L" + ? leftHandler(sum.v) + : rightHandler(sum.v); diff --git a/lib/structures/sum.types.js b/lib/structures/sum.types.js new file mode 100644 index 0000000..b9a8b20 --- /dev/null +++ b/lib/structures/sum.types.js @@ -0,0 +1,10 @@ +import { getDefaultTypeParser }from "../parser/type_parser.js"; +import { match, newLeft, newRight } from "./sum.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleSum = [ + { i: newLeft , t: mkType("∀a,b: a -> (a + b)") }, + { i: newRight , t: mkType("∀a,b: b -> (a + b)") }, + { i: match , t: mkType("∀a,b,c: (a + b) -> (a -> c) -> (b -> c) -> c") }, +]; diff --git a/lib/structures/type_constructors.js b/lib/structures/type_constructors.js new file mode 100644 index 0000000..0ec9b65 --- /dev/null +++ b/lib/structures/type_constructors.js @@ -0,0 +1,17 @@ +// to break up dependency cycles, type constructors are defined in their own JS module + +import { makeTypeConstructor } from "../meta/type_constructor.js"; + +export const symbolFunction = "Function__c2433e31fa574a2cb3b6b5d62ac9d4b2"; +export const symbolSum = "Sum__89b731efa6344ea0b6a8663a45cf3ea8"; +export const symbolProduct = "Product__89351ecdedfb4b05b2a5a6cc0c383e12"; +export const symbolList = "List__daa8de8a9047435e96034ec64f2da3a1"; +export const symbolSet = "Set__8fef2c1873df4327ac31bd61d2ecf7e0"; +export const symbolDict = "Dict__d7158547322549ac9f7f8176aec123dd"; + +export const fnType = makeTypeConstructor(symbolFunction)(2); +export const sumType = makeTypeConstructor(symbolSum)(2); +export const prodType = makeTypeConstructor(symbolProduct)(2); +export const lsType = makeTypeConstructor(symbolList)(1); +export const setType = makeTypeConstructor(symbolSet)(1); +export const dictType = makeTypeConstructor(symbolDict)(2); diff --git a/lib/structures/type_constructors.types.js b/lib/structures/type_constructors.types.js new file mode 100644 index 0000000..04c6eb6 --- /dev/null +++ b/lib/structures/type_constructors.types.js @@ -0,0 +1,23 @@ +import { getDefaultTypeParser } from "../parser/type_parser.js"; +import { SymbolT } from "../primitives/primitive_types.js"; +import { dictType, fnType, lsType, prodType, setType, sumType, symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from "./type_constructors.js"; + +const mkType = getDefaultTypeParser(); + +export const ModuleStructuralSymbols = [ + { i: symbolSet , t: SymbolT }, + { i: symbolList , t: SymbolT }, + { i: symbolProduct , t: SymbolT }, + { i: symbolSum , t: SymbolT }, + { i: symbolDict , t: SymbolT }, + { i: symbolFunction , t: SymbolT }, +]; + +export const ModuleTypeConstructors = [ + { i: setType , t: mkType("Type -> Type") }, + { i: lsType , t: mkType("Type -> Type") }, + { i: prodType , t: mkType("Type -> Type -> Type") }, + { i: sumType , t: mkType("Type -> Type -> Type") }, + { i: dictType , t: mkType("Type -> Type -> Type") }, + { i: fnType , t: mkType("Type -> Type -> Type") }, +]; \ No newline at end of file diff --git a/lib/symbol.js b/lib/symbol.js deleted file mode 100644 index 939dc93..0000000 --- a/lib/symbol.js +++ /dev/null @@ -1,21 +0,0 @@ -import { eqSymbol, getName } from "../primitives/symbol.js"; -import { Bool, SymbolT, Type } from "../primitives/types.js"; -import { String } from "../structures/list.js"; -import { typedFnType } from "../structures/types.js"; - -// The way instances of SymbolT are currently encoded, their constructor is not a valid DOPE function, because it is impure. -// The only way to construct symbols is to do it in JS code. - -// At some point, we should start encoding SymbolTs as UUIDs rather than JS-Symbols. - -export const ModuleSymbol = {l:[ - {i: SymbolT, t: Type}, - - ...typedFnType(getName, fnType => - fnType - (SymbolT) - (String) - ), - - ...typedFnType(eqSymbol, fnType => fnType(() => SymbolT)(fnType(SymbolT)(() => Bool))), -]}; diff --git a/lib/type_constructor.js b/lib/type_constructor.js deleted file mode 100644 index 99ee760..0000000 --- a/lib/type_constructor.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Int, SymbolT, Type } from "../primitives/types.js"; -import { typedFnType } from "../structures/types.js"; -import { makeTypeConstructor } from "../type_constructor.js"; - -// This function and its type signature cannot be in the same file as 'makeTypeConstructor' because then we get an import cycle among JS modules. -export const ModuleTypeConstructor = {l:[ - ...typedFnType(makeTypeConstructor, fnType => - fnType - (SymbolT) - (fnType - (Int) - (Type) - )), -]}; diff --git a/lib/util/defaultmap.js b/lib/util/defaultmap.js new file mode 100644 index 0000000..e863962 --- /dev/null +++ b/lib/util/defaultmap.js @@ -0,0 +1,18 @@ + +// export class DefaultMap { +// constructor(defaultValue, ...rest) { +// this.defaultValue = defaultValue; +// this.m = new Map(rest); +// } +// getdefault(key, addToMapping = false) { +// return this.m.get(key) || (() => { +// const val = this.defaultValue(key); +// if (addToMapping) +// this.m.set(key, val); +// return val; +// })(); +// } +// entries() { +// return this.m.entries(); +// } +// } diff --git a/util/pretty.js b/lib/util/pretty.js similarity index 72% rename from util/pretty.js rename to lib/util/pretty.js index 05865d9..b8e09cf 100644 --- a/util/pretty.js +++ b/lib/util/pretty.js @@ -1,17 +1,23 @@ import { inspect } from 'node:util'; -import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from '../structures/types.js'; +import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSum } from '../structures/type_constructors.js'; +import { symbolSet } from "../structures/type_constructors.js"; import { mapRecursiveStructure } from './util.js'; +import { getHumanReadableName } from '../primitives/symbol.js'; export function pretty(obj) { return inspect(obj, { colors: true, depth: null, breakLength: 120 }); } -// Pretty print type +// Pretty print Type export const prettyT = type => { return mapRecursiveStructure(([type, m, seen], map) => { if (typeof type === "symbol") { + // type variable return type.description; } + // if (type.params === undefined) { + // throw new Error("parameter is not a Type ... did you mean to call prettyGenT instead?") + // } 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])()); @@ -19,12 +25,10 @@ export const prettyT = type => { const annot = seen.has(type) ? seen.get(type) : ``; return renderType(type.symbol, annot, params); } - else { - if (!seen.has(type)) { - seen.set(type, `#${seen.size}`); - } - return seen.get(type); + if (!seen.has(type)) { + seen.set(type, `#${seen.size}`); } + return seen.get(type); })([type, new Set(), new Map()])(); }; @@ -36,9 +40,10 @@ const renderType = (symbol, annot, params) => { [symbolSum] : `${annot}(${params[0]} + ${params[1]})`, [symbolProduct] : `${annot}(${params[0]} ⨯ ${params[1]})`, [symbolDict] : `${annot}(${params[0]} => ${params[1]})`, - }[symbol] || symbol.description; + }[symbol] || getHumanReadableName(symbol); }; +// Pretty print GenericType export const prettyGenT = genericType => { return `∀${[...genericType.typeVars].map(prettyT).sort((a, b) => a.localeCompare(b)).join(",")}: ${prettyT(genericType.type)}`; }; diff --git a/lib/util/random.js b/lib/util/random.js new file mode 100644 index 0000000..81d659e --- /dev/null +++ b/lib/util/random.js @@ -0,0 +1,16 @@ +// IMPURE +export const genUUID = (len=16) => { + const arr = crypto.getRandomValues(new Uint8Array(len)); + let result = ""; + for (let i=0; ivalue)-pairs instead of the tree structure. + +import createRBTree from "functional-red-black-tree"; +import { inspect } from "util"; + +export class RBTreeWrapper { + constructor(tree) { + this.tree = tree; + } + + static new(compareFn) { + return new RBTreeWrapper(createRBTree(compareFn)) + } + + // pretty print to console + [inspect.custom](depth, options, inspect) { + const entries = []; + this.tree.forEach((key, val) => { entries.push(`${inspect(key)} => ${inspect(val)}`); }); + return `RBTree(${this.tree.length}) {${entries.join(', ')}}`; + } +} diff --git a/lib/util/util.js b/lib/util/util.js new file mode 100644 index 0000000..4e401ca --- /dev/null +++ b/lib/util/util.js @@ -0,0 +1,27 @@ +// zip two arrays +export function zip(a, b) { + return a.map((k, i) => [k, b[i]]); +} + +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()); diff --git a/versioning/slot.js b/lib/versioning/slot.js similarity index 96% rename from versioning/slot.js rename to lib/versioning/slot.js index 163c90c..49cd217 100644 --- a/versioning/slot.js +++ b/lib/versioning/slot.js @@ -1,4 +1,4 @@ -import { add, emptySet, forEach } from "../structures/set.js"; +import { add, emptySet, forEach } from "../../structures/set.js"; import { deepEqual } from "../util/util.js"; import {inspect} from "node:util"; import { compareSlots } from "../compare/versioning.js"; diff --git a/versioning/types.js b/lib/versioning/types.js similarity index 100% rename from versioning/types.js rename to lib/versioning/types.js diff --git a/versioning/value.js b/lib/versioning/value.js similarity index 100% rename from versioning/value.js rename to lib/versioning/value.js diff --git a/parser/parser.js b/parser/parser.js deleted file mode 100644 index ed7c91f..0000000 --- a/parser/parser.js +++ /dev/null @@ -1,174 +0,0 @@ -import { Bool, Char, Double, Int, Unit } from "../primitives/types.js"; -import { dictType, fnType, lsType, prodType, setType, sumType } from "../structures/types.js"; - -const bracketOperators = new Map([ - ['(', [')', null]], - ['[', [']', lsType]], - ['{', ['}', setType]], - - // can only occur at beginning - // we use these to extract the type variables - ['∀', [':', null]], -]); - -const infixOperators = new Map([ - ['+', sumType], - ['|', sumType], - ['⨯', prodType], - ['*', prodType], - ['→', fnType], - ['->', fnType], - ['⇒', dictType], - ['=>', dictType], - - // only used for type variables (e.g., ∀a,b,c:) - [',', fnX => fnY => { - const x = fnX(); - const y = fnY(); - return Array.isArray(x) ? x.concat(y) : [x].concat(y) - }], -]); - -const a = Symbol('a'); -const b = Symbol('b'); -const c = Symbol('c'); -const d = Symbol('d'); -const e = Symbol('e'); - -const primitives = new Map([ - ['Int', Int], - ['Double', Double], - ['Bool', Bool], - ['Char', Char], - ['Unit', Unit], - ['a', a], - ['b', b], - ['c', c], - ['d', d], - ['e', e], -]); - -const TOKENS = [ - ...bracketOperators.keys(), - ...[...bracketOperators.values()].map(v => v[0]), - ...infixOperators.keys(), - ...primitives.keys(), -]; - -// console.log('TOKENS =', TOKENS); - -const tokenize = expr => { - const tokens = []; - let i=0; - outerloop: while (i { - const bracket = bracketOperators.get(tokens[0]); - if (bracket === undefined) { - // no group, just a single token: - const [firstToken, ...rest] = tokens; - return [[firstToken], null, rest]; - } - else { - // find where group ends: - const [closing, fn] = bracket; - const opening = tokens[0] - let depth = 1; - let i = 1; - for (; i { - // console.log('parseGroup ', tokensInGroup, fn); - return (fn === null) - ? __parse(tokensInGroup, labels, label) - : fn(self => { - return __parse(tokensInGroup, extendLabels(labels, label, self)); - }); -} - -const extendLabels = (labels, label, self) => { - return (label === null) ? labels : new Map([...labels, [label, self]]) -}; - -const __parse = (tokens, labels = new Map(), label = null) => { - // console.log('parse ', tokens); - if (tokens[0].startsWith('#')) { - if (labels.has(tokens[0])) { - return labels.get(tokens[0]); - } - else { - // pass label and parse 'rest' - return __parse(tokens.slice(1), labels, tokens[0]); - } - } - if (tokens.length === 1) { - return primitives.get(tokens[0]); - } - else { - const [lhsTokens, fnGrp, rest] = consumeGroup(tokens); - if (rest.length === 0) { - return parseGroup(lhsTokens, fnGrp, labels, label); - } - const [operator, ...rhsTokens] = rest; - for (const [operatorChar, fn] of infixOperators) { - if (operator === operatorChar) { - return fn - (self => { - return parseGroup(lhsTokens, fnGrp, extendLabels(labels, label, self)); - })(self => { - return __parse(rhsTokens, extendLabels(labels, label, self)); - }); - } - } - throw new Error("unknown operator: "+operator) - } -}; - -export const parse = expr => { - const tokens = tokenize(expr); - if (tokens[0] === '∀') { - // generic type - const [typeVarTokens, _, rest] = consumeGroup(tokens); - const typeVars = [].concat(__parse(typeVarTokens)) - const type = __parse(rest); - return { typeVars, type }; - } - return __parse(tokens); -} diff --git a/primitives/bool.js b/primitives/bool.js deleted file mode 100644 index 1a1495c..0000000 --- a/primitives/bool.js +++ /dev/null @@ -1,15 +0,0 @@ -import { fnType, typedFnType } from "../structures/types.js"; -import { Type } from "./types.js"; -import { Bool } from "./types.js"; - -const eqBool = x => y => x === y; - -export const ModuleBool = {l:[ - {i: true , t: Bool}, - {i: false, t: Bool}, - - {i: Bool , t: Type}, - - // Bool -> Bool -> Bool - ...typedFnType(eqBool, fnType => fnType(() => Bool)(fnType(Bool)(() => Bool))), -]}; diff --git a/primitives/byte.js b/primitives/byte.js deleted file mode 100644 index 0ff79bc..0000000 --- a/primitives/byte.js +++ /dev/null @@ -1,11 +0,0 @@ -import { typedFnType } from "../structures/types.js"; -import { Type } from "./types.js"; -import {Byte, Bool} from "./types.js"; - -const eqByte = x => y => x === y; - -export const ModuleByte = {l:[ - {i: Byte , t: Type }, - - ...typedFnType(eqByte, fnType => fnType(() => Byte)(fnType(Byte)(() => Bool))), -]}; diff --git a/primitives/char.js b/primitives/char.js deleted file mode 100644 index 11d355f..0000000 --- a/primitives/char.js +++ /dev/null @@ -1,16 +0,0 @@ -import { typedFnType } from "../structures/types.js"; -import { Type } from "./types.js"; -import {Char, Bool} from "./types.js"; - -const eq = x => y => x === y; - -export const ModuleChar = {l:[ - {i: Char, t: Type}, - - ...typedFnType(eq, fnType => - fnType - (Char) - (fnType - (Char) - (Bool))), -]}; diff --git a/primitives/double.js b/primitives/double.js deleted file mode 100644 index 7de914c..0000000 --- a/primitives/double.js +++ /dev/null @@ -1,15 +0,0 @@ -import { typedFnType } from "../structures/types.js"; -import { Type } from "./types.js"; -import {Bool, Double} from "./types.js"; - -export const addDouble = x => y => x + y; -export const mulDouble = x => y => x * y; -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))), -]}; diff --git a/primitives/dynamic.js b/primitives/dynamic.js deleted file mode 100644 index bd6584f..0000000 --- a/primitives/dynamic.js +++ /dev/null @@ -1,19 +0,0 @@ -import { typedFnType } from "../structures/types.js"; -import { Top, Type } from "./types.js"; -import { makeTypeConstructor } from "../type_constructor.js"; - -// A type-link, connecting a value to its Type. -export const symbolDynamic = Symbol('Dynamic'); -export const Dynamic = makeTypeConstructor(symbolDynamic)(0); - -export const getInst = lnk => lnk.i; -export const getType = lnk => lnk.t; - -export const ModuleDynamic = {l:[ - {i: Dynamic, t: Type}, - {i: Top , t: Type}, - - ...typedFnType(getInst, fnType => fnType(() => Dynamic)(() => Top)), - ...typedFnType(getType, fnType => fnType(() => Dynamic)(() => Top)), -]}; - diff --git a/primitives/generic_type.js b/primitives/generic_type.js deleted file mode 100644 index 580c34b..0000000 --- a/primitives/generic_type.js +++ /dev/null @@ -1,21 +0,0 @@ -import { newLeft, newRight } from "../structures/sum.js"; -import { setType, sumType, typedFnType } from "../structures/types.js"; -import { Top, GenericType, SymbolT, Type, Unit } from "./types.js"; -import { unit } from "./unit.js"; - -export const getType = genericType => genericType.type; -export const getTypeVars = genericType => genericType.typeVars; - -export const toNonGeneric = genericType => (genericType.typeVars.size === 0) - ? newRight(genericType.type) - : newLeft(unit); - -export const ModuleGenericType = {l:[ - {i: GenericType, t: Top}, - - // ...typedFnType(getType, fnType => fnType(() => GenericType)(() => Type)), - - // ...typedFnType(getTypeVars, fnType => fnType(() => GenericType)(() => set(() => SymbolT))), - - ...typedFnType(toNonGeneric, fnType => fnType(() => GenericType)(sumType(() => Unit)(() => () => Type))), -]}; diff --git a/primitives/int.js b/primitives/int.js deleted file mode 100644 index d4eb833..0000000 --- a/primitives/int.js +++ /dev/null @@ -1,19 +0,0 @@ -import { typedFnType } from "../structures/types.js"; -import { Type } from "./types.js"; - -import {Bool, Int} from "./types.js"; - -export const addInt = x => y => x + y; -export const mulInt = x => y => x * y; -export const eqInt = x => y => x === y; - -const serialize = x => x.toString(); -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))), -]}; diff --git a/primitives/symbol.js b/primitives/symbol.js deleted file mode 100644 index b0e56ad..0000000 --- a/primitives/symbol.js +++ /dev/null @@ -1,8 +0,0 @@ -// The functions are only defined here. For their types, see lib/symbol.js - -// Cannot turn the constructor into a DOPE function, because it is NOT PURE: -// export const constructSymbol = name => Symbol(name); - -export const getName = symbol => symbol.description; - -export const eqSymbol = a => b => a === b; diff --git a/primitives/type.js b/primitives/type.js deleted file mode 100644 index a8dbd11..0000000 --- a/primitives/type.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Bool, SymbolT, Type } from "./types.js"; -import { isFunction, lsType, typedFnType } from "../structures/types.js"; -import { getSymbol, getParams } from "../type_constructor.js"; -import { deepEqual } from "../util/util.js"; - -// we can test whether types are equal: -export const eqType = t1 => t2 => deepEqual(t1, t2); - -// a module is just a set of typed objects -// each 'typed object' is implicitly an instance of TypeLink (defined below) -export const ModuleType = {l:[ - // TODO? maybe follow Lean so - // Type.{0} : Type.{1} - // Type.{1} : Type.{2} - // ... - // see: https://lean-lang.org/functional_programming_in_lean/functor-applicative-monad/universes.html - - {i: Type, t: Type}, - - // Type -> Type -> Bool - ...typedFnType(eqType, fnType => - fnType - (Type) - (fnType - (Type) - (Bool) - )), - - ...typedFnType(getSymbol, fnType => fnType(() => Type)(() => SymbolT)), - ...typedFnType(getParams, fnType => fnType(() => Type)(() => lsType(() =>Type))), - - ...typedFnType(isFunction, fnType => fnType(() => Type)(() => Bool)), -]}; \ No newline at end of file diff --git a/primitives/types.js b/primitives/types.js deleted file mode 100644 index f8e05f4..0000000 --- a/primitives/types.js +++ /dev/null @@ -1,51 +0,0 @@ -// to break up dependency cycles, primitive types are defined in their own JS module - -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 symbolTop = Symbol('⊤'); -export const SymbolGenericType = Symbol('GenericType'); - -export const Int = makeTypeConstructor(SymbolInt)(0); -export const Bool = makeTypeConstructor(SymbolBool)(0); -export const Double = makeTypeConstructor(SymbolDouble)(0); -export const Byte = makeTypeConstructor(SymbolByte)(0); -export const Char = makeTypeConstructor(SymbolChar)(0); - -// Unit type has only 1 instance, the empty tuple. -export const Unit = makeTypeConstructor(SymbolUnit)(0); - -// Bottom type has no instances. -export const Bottom = makeTypeConstructor(SymbolBottom)(0); - -export const SymbolT = makeTypeConstructor(SymbolSymbol)(0); - -// Types are typed by Top -export const Type = makeTypeConstructor(SymbolType)(0); - -export const GenericType = makeTypeConstructor(SymbolGenericType)(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: SymbolGenericType, t: SymbolT}, - {i: symbolTop , t: SymbolT}, -]}; diff --git a/primitives/unit.js b/primitives/unit.js deleted file mode 100644 index 99f1b30..0000000 --- a/primitives/unit.js +++ /dev/null @@ -1,15 +0,0 @@ -import { typedFnType } from "../structures/types.js"; -import { Bool, Type, Unit } from "./types.js"; - -export const eqUnit = x => y => x === y; - -export const unit = {}; - -export const ModuleUnit = {l:[ - {i: unit, t: Unit}, - - {i: Unit, t: Type}, - - // Unit -> Unit -> Bool - ...typedFnType(eqUnit, fnType => fnType(() => Unit)(fnType(Unit)(() => Bool))), -]}; diff --git a/stdlib.js b/stdlib.js deleted file mode 100644 index 0c5e726..0000000 --- a/stdlib.js +++ /dev/null @@ -1,42 +0,0 @@ -import { ModuleSymbol } from "./lib/symbol.js"; -import { ModuleTypeConstructor } from "./lib/type_constructor.js"; -import { ModuleBool } from "./primitives/bool.js"; -import { ModuleByte } from "./primitives/byte.js"; -import { ModuleChar } from "./primitives/char.js"; -import { ModuleDouble } from "./primitives/double.js"; -import { ModuleInt } from "./primitives/int.js"; -import { ModuleSymbols } from "./primitives/types.js"; -import { ModuleUnit } from "./primitives/unit.js"; -import { ModuleFunction } from "./structures/function.js"; -import { ModuleList } from "./structures/list.js"; -import { ModuleProduct } from "./structures/product.js"; -import { ModuleSum } from "./structures/sum.js"; -import { ModuleType } from "./primitives/type.js"; -import { ModuleDynamic } from "./primitives/dynamic.js"; -import { ModuleSet } from "./structures/set.js"; -import { ModuleGenericType } from "./primitives/generic_type.js"; - -export const ModuleStd = {l:[ - ...ModuleType.l, - ...ModuleGenericType.l, - ...ModuleDynamic.l, - - ...ModuleTypeConstructor.l, - ...ModuleSymbols.l, - - // Primitive types - ...ModuleBool.l, - ...ModuleByte.l, - ...ModuleChar.l, - ...ModuleDouble.l, - ...ModuleInt.l, - ...ModuleSymbol.l, - ...ModuleUnit.l, - - // Types that consist of other types - ...ModuleFunction.l, - ...ModuleList.l, - ...ModuleProduct.l, - ...ModuleSum.l, - ...ModuleSet.l, -]}; diff --git a/structures/function.js b/structures/function.js deleted file mode 100644 index 38424df..0000000 --- a/structures/function.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Type } from "../primitives/types.js"; -import { typedFnType } from "./types.js"; -import { fnType } from "./types.js"; - -export const ModuleFunction = {l:[ - // binary type constructor: Type -> Type -> Type - ...typedFnType(fnType, fnType => fnType - /* in */ (Type) - /* out */ (fnType - /* in */ (Type) - /* out */ (Type) - ) - ), -]}; diff --git a/structures/list.js b/structures/list.js deleted file mode 100644 index 406edba..0000000 --- a/structures/list.js +++ /dev/null @@ -1,82 +0,0 @@ -import { typedFnType } from "./types.js"; -import { Char, GenericType, Type } from "../primitives/types.js"; -import { Int } from "../primitives/types.js"; -import { makeGeneric } from "../generics/generics.js"; -import { lsType } from "./types.js"; -import { Dynamic } from "../primitives/dynamic.js" - -// 'normal' implementation -export const emptyList = {l:[]}; -// 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])}); -export const map = ls => fn => ({ l: ls.l.map(elem => fn(elem)) }); -export const length = ls => ls.l.length; -export const fold = ls => callback => initial => { - let acc = initial; - for (let i=0; iChar); // alias -export const Module = lsType(() =>Dynamic); - -export const ModuleList = {l:[ - // // Type -> Type - // ...typedFnType(lsType, fnType => - // fnType - // /* in */ (Type) - // /* out */ (Type) - // ), - - // // [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 -> [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 -> b) -> [b] - // ...typedFnType(map, fnType => - // makeGeneric((a, b) => - // fnType - // (() => lsType(() => a)) - // (() => fnType - // (() => fnType(() => a)(() => b)) - // (() => lsType(() => b)) - // )), GenericType), -]}; diff --git a/structures/product.types.js b/structures/product.types.js deleted file mode 100644 index 5ba90bd..0000000 --- a/structures/product.types.js +++ /dev/null @@ -1,27 +0,0 @@ -import { makeGeneric } from "../generics/generics.js"; -import { Type, GenericType } from "../primitives/types.js"; -import { newProduct, getLeft, getRight } from "./product.js"; -import { typedFnType, prodType } from "./types.js"; - -export const ModuleProduct = { - l: [ - // binary type constructor - // Type -> Type -> Type - ...typedFnType(prodType, fnType => fnType(Type)(fnType(Type)(Type) - ) - ), - - // a -> b -> (a, b) - ...typedFnType(newProduct, fnType => makeGeneric((a, b) => fnType(a)(fnType(b)(prodType(() => a)(() => b)) - ) - ), GenericType), - - // (a, b) -> a - ...typedFnType(getLeft, fnType => makeGeneric((a, b) => fnType(prodType(() => a)(() => b))(a) - ), GenericType), - - // (a, b) -> b - ...typedFnType(getRight, fnType => makeGeneric((a, b) => fnType(prodType(() => a)(() => b))(b) - ), GenericType), - ] -}; diff --git a/structures/set.js b/structures/set.js deleted file mode 100644 index 00a6717..0000000 --- a/structures/set.js +++ /dev/null @@ -1,40 +0,0 @@ -import createRBTree from "functional-red-black-tree"; -import { inspect } from "node:util"; - -export class RBTreeWrapper { - constructor(tree) { - this.tree = tree; - } - // pretty print to console - [inspect.custom](depth, options, inspect) { - const entries = []; - this.tree.forEach((key,val) => {entries.push(`${inspect(key)} => ${inspect(val)}`);}); - return `RBTree(${this.tree.length}) {${entries.join(', ')}}`; - } -} - -// (a -> a -> Int) -> Set(a) -export const emptySet = compareFn => new RBTreeWrapper(createRBTree((x, y) => compareFn(x)(y))); - - -export const has = set => key => set.tree.get(key) === true; -export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWrapper(set.tree.insert(key, true)); -export const remove = set => key => new RBTreeWrapper(set.tree.remove(key)); -export const length = set => set.tree.length; - -export const first = set => set.tree.begin; -export const last = set => set.tree.end; - -// test if iterator is 'done', and if not, get element and advance iterator. -export const read = iter => ifNotDone => ifDone => { - if (iter !== undefined && iter.valid) { - return ifNotDone(iter.key)(iter.clone().next()); - } - else { - return ifDone(); - } -}; - -export const forEach = set => fn => { - set.tree.forEach(key => { fn(key); }); -}; diff --git a/structures/set.types.js b/structures/set.types.js deleted file mode 100644 index 6d900c3..0000000 --- a/structures/set.types.js +++ /dev/null @@ -1,31 +0,0 @@ -import { makeGeneric } from "../generics/generics.js"; -import { Int } from "../primitives/types.js"; -import { emptySet, has, add } from "./set.js"; -import { fnType, setType } from "./types.js"; - -const emptySetType = makeGeneric(a => - fnType - // comparison function: - (_ => fnType - (_ => a) - (_ => fnType(_ => a)(_ => Int))) - // the set: - (_ => setType(_ => a)) -); - -export const ModuleSet = { - l: [ - // Type -> Type - ...typedFnType(setType, fnType => fnType(_ => Type)(_ => Type) - ), - - { i: emptySet, t: emptySetType }, - { i: emptySetType, t: GenericType }, - - ...typedFnType(has, fnType => makeGeneric(a => fnType(_ => setType(_ => a))(_ => fnType(_ => a)(_ => Bool) - )), GenericType), - - ...typedFnType(add, fnType => makeGeneric(a => fnType(setType(_ => a))(fnType(a)(setType(_ => a)) - )), GenericType), - ] -}; diff --git a/structures/struct.js b/structures/struct.js deleted file mode 100644 index c2edd7f..0000000 --- a/structures/struct.js +++ /dev/null @@ -1,65 +0,0 @@ -import { Unit } from "../primitives/types.js"; -import { unit } from "../primitives/unit.js"; -import { capitalizeFirstLetter } from "../util/util.js"; -import { newProduct, getLeft, getRight } from "./product.js"; -import { fnType, prodType } from "./types.js"; - -// 'fields' is an array of (name: string, type: Type) pairs. -// e.g.: -// [{l: "x", r: Double}, {l: "y", r: Double}] -// results in the type (Double × (Double × Unit)) -export const structType = fields => { - if (fields.length === 0) { - return Unit; - } - const [field, ...rest] = fields; - const fieldType = getRight(field); - return prodType(() => fieldType)(() => structType(rest)); -}; - -export const makeConstructor = fields => { - const internal = (nParams, ret) => { - if (nParams === 0) { - const result = ret(unit); - return result; - } - return nextParam => { - const wrappedName = 'wrapped_' + ret.name; - const newRet = { - [wrappedName]: inner => newProduct(nextParam)(ret(inner)), - }[wrappedName]; - return internal(nParams-1, newRet); - } - }; - const id = x => x; - return internal(fields.length, id); -}; - -export const makeConstructorType = type => fields => { - if (fields.length === 0) { - return type; - } - const [field, ...rest] = fields; - const fieldType = getRight(field); - return fnType(() => fieldType)(() => makeConstructorType(rest)); -}; - -export const makeGetters = fields => { - if (fields.length === 0) { - return []; - } - const [field, ...rest] = fields; - const fieldName = getLeft(field); - const getterName = `get${capitalizeFirstLetter(fieldName)}`; - return [ - { [getterName]: obj => getLeft(obj) }[getterName], - ...makeGetters(rest).map(getter => ({[getter.name]: obj => getter(getRight(obj))}[getter.name])), - ]; -}; - -export const makeGettersTypes = type => fields => { - return fields.map(field => { - const fieldType = getRight(field); - return fnType(() => type)(() => fieldType); - }); -}; diff --git a/structures/sum.js b/structures/sum.js deleted file mode 100644 index f360af1..0000000 --- a/structures/sum.js +++ /dev/null @@ -1,62 +0,0 @@ -// Sum-type (also called: tagged union, disjoin union, variant type) -// A Sum-type always has only two variants, called "left" and "right". -// Sum-types of more variants (called Enums) can be constructed by nesting Sum-types. - -import { prodType } from "./types.js"; -import { GenericType, Type } from "../primitives/types.js"; -import { typedFnType } from "./types.js"; -import { makeGeneric } from "../generics/generics.js"; -import { sumType } from "./types.js"; - -export const newLeft = left => ({t: "L", v: left }); // 't': tag, 'v': value -export const newRight = right => ({t: "R", v: right}); - -// signature: -// sum-type -> (leftType -> resultType, rightType -> resultType) -> resultType -export const match = sum => handlers => - sum.t === "L" - ? handlers.l(sum.v) - : handlers.r(sum.v); - -export const ModuleSum = {l:[ - // binary type constructor - // Type -> Type -> Type - ...typedFnType(sumType, fnType => - fnType - (() => Type) - (() => fnType - (() => Type) - (() => Type) - ), - ), - - // // 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), - - // // 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 deleted file mode 100644 index 9a030ff..0000000 --- a/structures/types.js +++ /dev/null @@ -1,59 +0,0 @@ -// to break up dependency cycles, type constructors are defined in their own JS module - -import { Type } from "../primitives/types.js"; -import { getSymbol, makeTypeConstructor } from "../type_constructor.js"; - -// Function type - -// The registry ensures that we never accidentally create more than one JS object for the same function type. -// It is a cheap workaround for JS lacking customizable hash-functions and equality-testing-functions. -// This same pattern is repeated throughout the code for all non-nullary type constructors (list, sum, product, ...) -export const symbolFunction = Symbol('Function'); -export const fnType = makeTypeConstructor(symbolFunction)(2); - -export const isFunction = type => getSymbol(type) === symbolFunction; - -// Convenience function. Creates a function type, and also create Type-links for the function type (being typed by Function) and for all the nested function types. Saves a lot of code writing. -export const typedFnType = (instance, callback, typeOfType = Type) => { - const fnTs = []; - const wrappedFnType = inType => outType => { - const fnT = fnType(() => inType)(() => outType); - fnTs.push(fnT); - return fnT; - }; - const t = callback(wrappedFnType); // force evaluation - if (t.typeVars && typeOfType === Type) { - throw new Error("you probably meant to create a GenericType"); - } - const res = [ - { i: instance, t }, - { i: t , t: typeOfType }, - // ...fnTs.map(fnT => ({ i: fnT, t: Type })), - ]; - return res; -}; - -// Sum type - -export const symbolSum = Symbol("Sum"); -export const sumType = makeTypeConstructor(symbolSum)(2); - -// Product type - -export const symbolProduct = Symbol("Product"); -export const prodType = makeTypeConstructor(symbolProduct)(2); - -// List type - -export const symbolList = Symbol('List'); -export const lsType = makeTypeConstructor(symbolList)(1); - -// Set type - -export const symbolSet = Symbol('Set'); -export const setType = makeTypeConstructor(symbolSet)(1); - -// Dict type - -export const symbolDict = Symbol('Dict'); -export const dictType = makeTypeConstructor(symbolDict)(2); \ No newline at end of file diff --git a/type_constructor.js b/type_constructor.js deleted file mode 100644 index 9fda806..0000000 --- a/type_constructor.js +++ /dev/null @@ -1,59 +0,0 @@ -// import { DefaultMap } from "./util/defaultmap.js"; - -// const nullaryTypeConstructors = new DefaultMap( -// // symbol -> 0-ary type constructor (= a type, basically) -// symbol => ({ -// symbol, -// params: [], -// })); - - -// // 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 }; - } - // only for debugging, do we give the function a name - const fName = `${symbol.description.toLowerCase()}Type${params.length>0?params.length:''}`; - return { - [fName]: typeParam => { - if (typeof typeParam !== 'function') { - throw new Error("all type params must be functions"); - } - return __makeTypeConstructor(symbol, nAry-1, params.concat([typeParam])); - } - }[fName]; -} - -// Creates a new nominal type -// 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 deleted file mode 100644 index 05b7b4f..0000000 --- a/typeclasses/eq.js +++ /dev/null @@ -1,43 +0,0 @@ -import { makeGeneric } from "../generics/generics"; -import { GenericType, SymbolT, Type, Unit } from "../primitives/types"; -import { typedFnType } from "../structures/types"; -import { Bool, Byte, Char, Double, Int } from "../primitives/types"; -import { deepEqual } from "../util/util"; -import { eqDictType } from "./eq_dict"; - -export const getEq = numDict => numDict.eq; - -export const ModuleEq = {l:[ - // type constructor: Type -> Type - ...typedFnType(eqDictType, fnType => fnType(() => Type)(() => Type)), - - // (EqDict a) -> a -> a -> Bool - ...typedFnType(getEq, fnType => - makeGeneric(a => - fnType - (eqDictType(a)) - (fnType - (a) - (fnType - (a) - (Bool) - ) - )), GenericType), -]}; - -// all our data (and types) are encoded such that we can test equality the same way: - -const eq = x => y => deepEqual(x,y); - -const eqDict = {eq}; - -export const EqInstances = new Map([ - [Int , eqDict], - [Bool , eqDict], - [Double , eqDict], - [Byte , eqDict], - [Char , eqDict], - [Unit , eqDict], - [Type , eqDict], - [SymbolT, eqDict], -]); diff --git a/typeclasses/eq_dict.js b/typeclasses/eq_dict.js deleted file mode 100644 index 7fbd01f..0000000 --- a/typeclasses/eq_dict.js +++ /dev/null @@ -1,4 +0,0 @@ -import { makeTypeConstructor } from "../type_constructor.js"; - -const eqDictSymbol = Symbol('EqDict'); -export const eqDictType = makeTypeConstructor(eqDictSymbol)(1); \ No newline at end of file diff --git a/typeclasses/num.js b/typeclasses/num.js deleted file mode 100644 index e78f10c..0000000 --- a/typeclasses/num.js +++ /dev/null @@ -1,55 +0,0 @@ -import { makeGeneric } from "../generics/generics.js"; -import { addDouble, mulDouble } from "../primitives/double.js"; -import { addInt, mulInt } from "../primitives/int.js"; -import { Type } from "../primitives/types.js"; -import { typedFnType, typedFnType2 } from "../structures/types.js"; -import { Double, Int } from "../primitives/types.js"; -import { numDictType } from "./num_type.js"; - -export const getAdd = numDict => numDict.add; -export const getMul = numDict => numDict.mul; - -// getAdd and getMul have same (generic) type: -// NumDict a -> a -> a -> a -const [getAddMulFnType, typesOfFns] = typedFnType2(fnType => - makeGeneric(a => - fnType - (numDictType(a)) - (fnType - (a) - (fnType(() => a)(() => a)) - ))); - -export const ModuleNum = {l:[ - ...typedFnType(numDictType, fnType => fnType({in: Type, out: Type})), - - {i: getAdd, t: getAddMulFnType}, - {i: getMul, t: getAddMulFnType}, - - ...typesOfFns, -]}; - - -const IntNumDict = { - add: addInt, - mul: mulInt, -}; - -const DoubleNumDict = { - add: addDouble, - mul: mulDouble, -} - -export const ModuleNumInstances = {l:[ - {i: IntNumDict , t: numDictType(Int)}, - {i: DoubleNumDict, t: numDictType(Double)}, -]}; - -// mapping from type to type class implementation -// in Haskell, such a mapping is global (for the entire application being compiled), and every type can implement every type class at most once. -// in Lean, such mappings can be local, and there can be multiple implementations per (type, type class). -// We have to follow Lean's approach, because in DOPE, there is no such thing as a "global scope". Every dependency is explicit, and type class resolution is just a function that always depends on a specific mapping: -export const NumInstances = new Map([ - [Int , IntNumDict ], - [Double, DoubleNumDict], -]); diff --git a/typeclasses/num_dict.js b/typeclasses/num_dict.js deleted file mode 100644 index bf59390..0000000 --- a/typeclasses/num_dict.js +++ /dev/null @@ -1,4 +0,0 @@ -import { makeTypeConstructor } from "../type_constructor.js"; - -const numDictSymbol = Symbol("NumDict"); -export const numDictType = makeTypeConstructor(numDictSymbol)(1); diff --git a/typeclasses/show.js b/typeclasses/show.js deleted file mode 100644 index 9d9b6df..0000000 --- a/typeclasses/show.js +++ /dev/null @@ -1,9 +0,0 @@ -import { cart2Str, NPoint2DCartesian, NPoint2DPolar, polar2Str } from "../lib/point.js"; -import { Type } from "../primitives/types.js"; -import { prettyT } from "../structures/types.js"; - -export const ShowInstances = new Map([ - [Type , {show: prettyT}], - [NPoint2DCartesian, {show: cart2Str}], - [NPoint2DPolar , {show: polar2Str}], -]); diff --git a/typeclasses/show_dict.js b/typeclasses/show_dict.js deleted file mode 100644 index 83166f0..0000000 --- a/typeclasses/show_dict.js +++ /dev/null @@ -1,5 +0,0 @@ -import { makeTypeConstructor } from "../type_constructor.js"; - -const showDictSymbol = Symbol('ShowDict'); -export const showDictType = makeTypeConstructor(showDictSymbol)(1); - diff --git a/util/defaultmap.js b/util/defaultmap.js deleted file mode 100644 index 6d40c66..0000000 --- a/util/defaultmap.js +++ /dev/null @@ -1,18 +0,0 @@ - -export class DefaultMap { - constructor(defaultValue, ...rest) { - this.defaultValue = defaultValue; - this.m = new Map(rest); - } - getdefault(key, addToMapping = false) { - return this.m.get(key) || (() => { - const val = this.defaultValue(key); - if (addToMapping) - this.m.set(key, val); - return val; - })(); - } - entries() { - return this.m.entries(); - } -} diff --git a/util/util.js b/util/util.js deleted file mode 100644 index 66cbde6..0000000 --- a/util/util.js +++ /dev/null @@ -1,101 +0,0 @@ -import { lsType, setType } from "../structures/types.js"; -import { pretty } from "./pretty.js"; - -// re-inventing the wheel: -export function deepEqual(a, b) { - if (a === b) return true; // <- shallow equality and primitives - if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) { - return false; - } - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) { - if (!deepEqual(a[i], b[i])) return false; - } - return true; - } - if (a instanceof Set) { - if (!(b instanceof Set)) { - return false; - } - if (a.size !== b.size) { - return false; - } - for (const entry of a) { - if (!b.has(entry)) { - return false; - } - } - return true; - } - const keysA = Object.keys(a); - const keysB = Object.keys(b); - if (keysA.length !== keysB.length) return false; - for (let key of keysA) { - if (!keysB.includes(key) || !deepEqual(a[key], b[key])) { - return false; - } - } - return true; -} - -// zip two arrays -export function zip(a, b) { - return a.map((k, i) => [k, b[i]]); -} - -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](); -// // }