diff --git a/generics/generics.js b/generics/generics.js index f42896a..58cd914 100644 --- a/generics/generics.js +++ b/generics/generics.js @@ -1,6 +1,21 @@ -import { fnType, lsType } from "../type_registry.js"; +import { lsType } from "../structures/list_common.js"; +import { fnType } from "../metacircular.js"; import { deepEqual, pretty } from "../util.js"; +const genericTypeRegistry = new DefaultMap(underlyingType => ({generic: underlyingType})); + +// type constructor for generic kinds, +// for instance: +// a -> a -> Bool +// is typed by +// genericType(Function) +export const genericType = underlyingType => genericTypeRegistry.getdefault(underlyingType, true); + +// constructor for generic types +// for instance, the type: +// a -> a -> Bool +// is created by +// makeGeneric(a => fnType({in: a, out: fnType({in: a, out: Bool})})) export const makeGeneric = callback => { // type variables to make available: const a = Symbol('a'); @@ -32,14 +47,11 @@ const occurring = (type, typeVars) => { return new Set(); } -// merge_int(1, 2) => conflict(1,2) -// merge_list_of_int([1], [2]) => [conflict(1,2)] +// Currently very ad-hoc. -// merge {i: [1], t: List_of_Int} -> - -// Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog: +// Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog (hence the function name): // https://www.dai.ed.ac.uk/groups/ssp/bookpages/quickprolog/node12.html -export const matchGeneric = ( +export const unify = ( {typeVars: formalTypeVars, type: formalType}, {typeVars: actualTypeVars, type: actualType}, ) => { @@ -71,8 +83,8 @@ export const matchGeneric = ( } else { // both are function type - const inType = matchGeneric({typeVars: formalTypeVars, type: formalType.in}, {typeVars: actualTypeVars, type: actualType.in}); - const outType = matchGeneric({typeVars: formalTypeVars, type: formalType.out}, {typeVars: actualTypeVars, type: actualType.out}); + const inType = unify({typeVars: formalTypeVars, type: formalType.in}, {typeVars: actualTypeVars, type: actualType.in}); + const outType = unify({typeVars: formalTypeVars, type: formalType.out}, {typeVars: actualTypeVars, type: actualType.out}); // check for conflicts between 'in' and 'out' subsitutions for (const [typeVar, actual] of inType.substitutions) { if (outType.substitutions.has(typeVar)) { @@ -104,7 +116,7 @@ export const matchGeneric = ( } else { // both are list type - const elementType = matchGeneric( + const elementType = unify( {typeVars: formalTypeVars, type: formalType.listOf}, {typeVars: actualTypeVars, type: actualType.listOf}); return { @@ -116,12 +128,16 @@ export const matchGeneric = ( }; } } + + if (formalType.numDict !== undefined) { + + } throw new Error("i don't know what to do :(") }; -export const matchConcrete = ({typeVars, type: formalType}, actualType) => { - return matchGeneric({typeVars, type: formalType}, {typeVars: new Set(), type: actualType}); -}; +// export const matchConcrete = ({typeVars, type: formalType}, actualType) => { +// return unify({typeVars, type: formalType}, {typeVars: new Set(), type: actualType}); +// }; export const substitute = (type, substitutions) => { if (substitutions.has(type)) { @@ -142,7 +158,7 @@ export const substitute = (type, substitutions) => { } export const assign = (genFnType, paramType) => { - const matchedInType = matchGeneric({ + const matchedInType = unify({ typeVars: genFnType.typeVars, type: genFnType.type.in, }, paramType); diff --git a/generics/generics.test.js b/generics/generics.test.js index 2930746..e20634d 100644 --- a/generics/generics.test.js +++ b/generics/generics.test.js @@ -1,20 +1,21 @@ import { Bool, Int } from "../primitives/symbols.js"; -import { fnType, lsType } from "../type_registry.js"; -import { assign, makeGeneric, matchConcrete, matchGeneric } from "./generics.js"; +import { lsType } from "../structures/list_common.js"; +import { fnType } from "../metacircular.js"; +import { assign, makeGeneric, unify } from "./generics.js"; // a -> Int const a_to_Int = makeGeneric(a => fnType({in: a, out: Int})); // Bool -> Int -const Bool_to_Int = fnType({in: lsType(Bool), out: Int}); +const Bool_to_Int = makeGeneric(() => fnType({in: lsType(Bool), out: Int})); // should be: Bool -> Int -console.log(matchConcrete(a_to_Int, Bool_to_Int)); +console.log(unify(a_to_Int, Bool_to_Int)); // (a -> a) -> b const fnType2 = makeGeneric((a,b) => fnType({in: fnType({in: a, out: a}), out: b})); // (Bool -> Bool) -> a const fnType3 = makeGeneric(a => fnType({in: fnType({in: Bool, out: Bool}), out: a})); // should be: (Bool -> Bool) -> a -console.log(matchGeneric(fnType2, fnType3)); +console.log(unify(fnType2, fnType3)); // (a -> b) -> [a] -> [b] const mapFnType = makeGeneric((a,b) => diff --git a/lib/id.js b/lib/id.js index 98c0411..6c27cf0 100644 --- a/lib/id.js +++ b/lib/id.js @@ -1,4 +1,4 @@ -import { fnType } from "../type_registry.js"; +import { fnType } from "../metacircular.js"; import {Function} from "../metacircular.js"; // import {Typed} from "../typed.js"; diff --git a/lib/point.js b/lib/point.js index d02dd57..5efabb5 100644 --- a/lib/point.js +++ b/lib/point.js @@ -1,7 +1,8 @@ import { Double } from "../primitives/symbols.js"; import { nominalType, NominalType } from "../structures/nominal_type.js"; import { makeProductType } from "../structures/product.js"; -import { prodType, typedFnType } from "../type_registry.js"; +import { typedFnType } from "../metacircular.js"; +import { prodType } from "../structures/product.js"; const PointCartesian2D = nominalType( "PointCartesian2D") diff --git a/lib/square.js b/lib/square.js index 9bb91cb..353c9a3 100644 --- a/lib/square.js +++ b/lib/square.js @@ -2,7 +2,7 @@ import {Function, getIn, getOut} from "../metacircular.js"; import {Module} from "../structures/list_types/module.js"; import { deepEqual } from "../util.js"; import { Typed } from "../typed.js"; -import { fnType } from "../type_registry.js"; +import { fnType } from "../metacircular.js"; // import {Num, NumDict, getType, getMul} from "../typeclasses/num.js"; diff --git a/main.js b/main.js index 8479df9..a006b0f 100644 --- a/main.js +++ b/main.js @@ -13,7 +13,9 @@ import {Int, Bool, Double, Byte} from "./primitives/symbols.js"; import { makeListModule } from "./structures/list_common.js"; import { makeProductType } from "./structures/product.js"; import { makeSumType } from "./structures/sum.js"; -import { lsType, prodType, sumType } from "./type_registry.js"; +import { sumType } from "./structures/sum.js"; +import { prodType } from "./structures/product.js"; +import { lsType } from "./structures/list_common.js"; class Context { constructor(mod) { diff --git a/metacircular.js b/metacircular.js index a068734..5070987 100644 --- a/metacircular.js +++ b/metacircular.js @@ -1,4 +1,12 @@ -import { fnType } from "./type_registry.js"; +import { DefaultMap } from "./util.js"; + +// 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, ...) +const fnTypeRegistry = new DefaultMap(inType => new DefaultMap(outType => ({ in: inType, out: outType }))); + +// type constructor for function types +export const fnType = ({ in: inType, out: outType }) => fnTypeRegistry.getdefault(inType, true).getdefault(outType, true); export const Type = Symbol('Type'); export const Function = Symbol('Function'); @@ -29,3 +37,30 @@ export const ModuleMetaCircular = {l:[ {i: getIn , t: fnType({in: Function, out: Type})}, {i: getOut, t: fnType({in: Function, out: Type})}, ]}; + + +// Wrapper around function below. +export const typedFnType = (instance, callback) => { + const [t, typesOfFns] = typedFnType2(callback); + const res = [ + { i: instance, t }, + ...typesOfFns, + ]; + return res; +}; + +// Create 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 typedFnType2 = callback => { + const fnTs = []; + const wrappedFnType = ({ in: inType, out: outType }) => { + console.log('wrappedFnType called'); + const fnT = fnType({ in: inType, out: outType }); + fnTs.push(fnT); + return fnT; + }; + const t = callback(wrappedFnType); // force evaluation + return [ + t, + fnTs.map(fnT => ({ i: fnT, t: Function })), + ]; +}; \ No newline at end of file diff --git a/primitives/bool.js b/primitives/bool.js index 5fbfb53..c15ee67 100644 --- a/primitives/bool.js +++ b/primitives/bool.js @@ -1,4 +1,4 @@ -import { fnType } from "../type_registry.js"; +import { fnType } from "../metacircular.js"; import {Type, Function} from "../metacircular.js"; import {Bool} from "./symbols.js"; diff --git a/primitives/double.js b/primitives/double.js index 2ada9d1..cbfa543 100644 --- a/primitives/double.js +++ b/primitives/double.js @@ -1,4 +1,4 @@ -import { fnType } from "../type_registry.js"; +import { fnType } from "../metacircular.js"; import {Type, Function} from "../metacircular.js"; import {Bool, Double} from "./symbols.js"; diff --git a/primitives/int.js b/primitives/int.js index 2bdc8b9..8367c87 100644 --- a/primitives/int.js +++ b/primitives/int.js @@ -1,4 +1,4 @@ -import { fnType } from "../type_registry.js"; +import { fnType } from "../metacircular.js"; import {Type, Function} from "../metacircular.js"; import {Bool, Int} from "./symbols.js"; diff --git a/structures/list.js b/structures/list.js index d4768a4..597021b 100644 --- a/structures/list.js +++ b/structures/list.js @@ -1,4 +1,5 @@ -import { fnType, lsType } from "../type_registry.js"; +import { lsType } from "./list_common.js"; +import { fnType } from "../metacircular.js"; import {Type, Function} from "../metacircular.js"; import { makeListModule } from "./list_common.js"; import { Module } from "./list_types/module.js"; diff --git a/structures/list_common.js b/structures/list_common.js index 742c776..f7429ad 100644 --- a/structures/list_common.js +++ b/structures/list_common.js @@ -1,6 +1,12 @@ -import { fnType, lsType } from "../type_registry.js"; +import { fnType } from "../metacircular.js"; import {Type, Function} from "../metacircular.js"; import {Int, Byte} from "../primitives/symbols.js"; +import { DefaultMap } from "../util.js"; + +const listTypeRegistry = new DefaultMap(elementType => ({ listOf: elementType })); + +// type constructor +export const lsType = elementType => listTypeRegistry.getdefault(elementType, true); // 'normal' implementation const emptyList = {l:[]}; @@ -66,4 +72,4 @@ export const makeListModule = elementType => { // {i: push , t: pushFnType}, ]}; } -}; +}; \ No newline at end of file diff --git a/structures/list_types/module.js b/structures/list_types/module.js index 87cae61..3d5ae3a 100644 --- a/structures/list_types/module.js +++ b/structures/list_types/module.js @@ -1,6 +1,6 @@ import { makeListModule } from "../list_common.js"; import { Typed } from "../../typed.js"; -import { lsType } from "../../type_registry.js"; +import { lsType } from "../list_common.js"; export const Module = lsType(Typed); // a Module is a list of Typeds diff --git a/structures/list_types/string.js b/structures/list_types/string.js index d25aa70..fcd1b92 100644 --- a/structures/list_types/string.js +++ b/structures/list_types/string.js @@ -1,5 +1,5 @@ import { Char } from "../../primitives/symbols.js"; -import { lsType } from "../../type_registry.js"; +import { lsType } from "../list_common.js"; import { makeListModule } from "../list_common.js"; export const String = lsType(Char); diff --git a/structures/nominal_type.js b/structures/nominal_type.js index 6ad7dcc..9785af0 100644 --- a/structures/nominal_type.js +++ b/structures/nominal_type.js @@ -1,6 +1,6 @@ import { Type } from "../metacircular.js"; import { String } from "../structures/list_types/string.js"; -import { prodType } from "../type_registry.js"; +import { prodType } from "./product.js"; import { makeProductType } from "./product.js"; export const NominalType = prodType(String, Type); diff --git a/structures/product.js b/structures/product.js index 5b98b48..f7f3f1e 100644 --- a/structures/product.js +++ b/structures/product.js @@ -1,5 +1,11 @@ -import { fnType, prodType } from "../type_registry.js"; +import { fnType } from "../metacircular.js"; import { Function, Type } from "../metacircular.js"; +import { DefaultMap } from "../util.js"; + +const productTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ operator: "product", leftType, rightType }))); + +// type constructor +export const prodType = (leftType, rightType) => productTypeRegistry.getdefault(leftType, true).getdefault(rightType, true); // In JS, all products are encoded in the same way: const constructor = left => right => ({left, right}); @@ -28,4 +34,4 @@ export const makeProductType = (leftType, rightType) => { {i: constructorType , t: Function}, {i: constructorBoundType, t: Function}, ]}; -}; +}; \ No newline at end of file diff --git a/structures/sum.js b/structures/sum.js index 709341c..eca45d6 100644 --- a/structures/sum.js +++ b/structures/sum.js @@ -1,7 +1,14 @@ -import { fnType, prodType, sumType } from "../type_registry.js"; +import { sumType } from "../type_registry.js"; +import { prodType } from "./product.js"; +import { fnType } from "../metacircular.js"; import { Function, Type } from "../metacircular.js"; import { Module } from "./list_types/module.js"; +const sumTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ operator: "sum", leftType, rightType }))); + +// type constructor +export const sumType = (leftType, rightType) => sumTypeRegistry.getdefault(leftType, true).getdefault(rightType, true); + const constructorLeft = left => ({variant: "L", value: left }); const constructorRight = right => ({variant: "R", value: right}); @@ -43,4 +50,4 @@ export const makeSumType = (leftType, rightType) => { {i: makeMatchFn, t: fnType({in: Type, out: Module})}, ]}; -}; +}; \ No newline at end of file diff --git a/type_registry.js b/type_registry.js deleted file mode 100644 index eb2ebb2..0000000 --- a/type_registry.js +++ /dev/null @@ -1,47 +0,0 @@ -// This module ensures that we never accidentally create more than one JS object for the same type twice. -// We do so by creating (nested) DefaultMap(s) for all non-nullary type constructors -// It is a cheap workaround for JS lacking customizable hash-functions and equality-testing-functions. - -import { Function } from "./metacircular.js"; -import { DefaultMap } from "./util.js"; - - -const listTypeRegistry = new DefaultMap(elementType => ({listOf: elementType})); -const genericTypeRegistry = new DefaultMap(underlyingType => ({generic: underlyingType})); -const fnTypeRegistry = new DefaultMap(inType => new DefaultMap(outType => ({in: inType, out: outType}))); -const productTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({operator: "product", leftType, rightType}))); -const sumTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({operator: "sum", leftType, rightType}))); - - -export const lsType = listTypeRegistry.getdefault.bind(listTypeRegistry); -export const genericType = genericTypeRegistry.getdefault.bind(genericTypeRegistry); -export const fnType = ({in: inType, out: outType}) => fnTypeRegistry.getdefault(inType, true).getdefault(outType, true); -export const sumType = (leftType, rightType) => sumTypeRegistry.getdefault(leftType, true).getdefault(rightType, true); -export const prodType = (leftType, rightType) => productTypeRegistry.getdefault(leftType, true).getdefault(rightType, true); - - -// Wrapper around function below. -export const typedFnType = (instance, callback) => { - const [t, typesOfFns] = typedFnType2(callback); - const res = [ - {i: instance, t}, - ...typesOfFns, - ]; - return res; -} - -// Create 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 typedFnType2 = callback => { - const fnTs = []; - const wrappedFnType = ({in: inType, out: outType}) => { - console.log('wrappedFnType called') - const fnT = fnType({in: inType, out: outType}); - fnTs.push(fnT); - return fnT; - } - const t = callback(wrappedFnType); // force evaluation - return [ - t, - fnTs.map(fnT => ({i: fnT, t: Function})), - ]; -} diff --git a/typeclasses/conformable.js b/typeclasses/conformable.js deleted file mode 100644 index 37b9f29..0000000 --- a/typeclasses/conformable.js +++ /dev/null @@ -1,33 +0,0 @@ -// import {Type, Function, makeTypedFnInOut, fnOut} from "../metacircular.js"; -// import {Bool} from "../primitives/symbols.js"; - -// export const Conformable = Symbol('Conformable'); -// export const conformanceCheck = Symbol('conformanceCheck'); - -// export const ModuleConformable = [ -// {i: Conformable , t: Type}, -// {i: conformanceCheck, t: Function}, - -// // Note: outType is just 'Type', we cannot use Bool because a function that has type 'conformanceCheck' will have outType Bool, therefore conformanceCheck must return the type of Bool, which is Type. -// ...makeTypedFnInOut({f: conformanceCheck, inType: Conformable, outType: Type}), -// ]; - - - -// //////////////////////////////////////////////////////// -// // Conformance check functions are themselves conformance checkable -// // This is because we want to restrict their return type to Bool - -// const conformanceCheckIsConform = fn => { -// return fnOut(fn) === Bool; -// }; - -// const conformanceCheckIsConformFn = {name: "isConform", inType: conformanceCheck, outType: Bool, fn: conformanceCheckIsConform}; - - -// export const ModuleConformanceCheckConforms = [ -// // instances of conformanceCheck (= 'isConform'-functions) are themselves conformance checkable -// {i: conformanceCheck, t: Conformable}, -// {i: conformanceCheckIsConformFn, t: conformanceCheck}, -// {i: conformanceCheckIsConformFn, t: Function}, -// ]; diff --git a/typeclasses/num.js b/typeclasses/num.js index 565eb6d..8996933 100644 --- a/typeclasses/num.js +++ b/typeclasses/num.js @@ -1,52 +1,54 @@ -// import {Type, Function, makeTypedFnInOut, fnIn, fnOut} from "../metacircular.js"; -// import {Conformable, conformanceCheck} from "./conformable.js"; -// import {Bool} from "../primitives/symbols.js"; -// import {deepEqual} from "../util.js"; +import { makeGeneric } from "../generics/generics"; +import { addDouble, mulDouble } from "../primitives/double"; +import { addInt, mulInt } from "../primitives/int"; +import { typedFnType, typedFnType2 } from "../metacircular"; -// export const Num = Symbol('Num'); -// export const NumDict = Symbol('NumDict'); -// // export const addType = Symbol('addType'); -// // export const addBoundType = Symbol('addBoundType'); -// // export const mulType = Symbol('mulType'); -// // export const mulBoundType = Symbol('mulBoundType'); +const numDictTypeRegistry = new DefaultMap(a => ({numDict: a})); +export const numDictType = a => numDictTypeRegistry.getdefault(a, true); -// // export const getType = numDict => numDict.type; +export const getAdd = numDict => numDict.add; +export const getMul = numDict => numDict.mul; -// export const getAdd = numDict => numDict.add; -// export const getMul = numDict => numDict.mul; +// getAdd and getMul have same (generic) type: +const [getAddMulFnType, typesOfFns] = typedFnType2(fnType => + makeGeneric(a => fnType({ + in: numDictType(a), + out: fnType({ + in: a, + out: fnType({in: a, out: a}), + }), + }))); -// // const addOrMulIsConform = fn => { -// // // function signature must be: a -> a -> a -// // const i0 = fnIn(fn); -// // const o0 = fnOut(fn); -// // const i1 = fnIn(o0); -// // const o1 = fnOut(o0); -// // return deepEqual(i0, i1) && deepEqual(i1, o1); -// // }; +export const ModuleNum = {l:[ + ...typedFnType(numDictType, fnType => fnType({in: Type, out: Type})), + + {i: getAdd, t: getAddMulFnType}, + {i: getMul, t: getAddMulFnType}, -// // const addIsConform = {name: "isConform", inType: addType, outType: Bool, fn: addOrMulIsConform}; -// // const mulIsConform = {name: "isConform", inType: mulType, outType: Bool, fn: addOrMulIsConform}; + ...typesOfFns, +]}; -// export const ModuleNum = [ -// {i: Num, t: Type}, -// {i: NumDict, t: Type}, -// {i: addType, t: Type}, -// {i: addBoundType, t: Type}, -// {i: mulType, t: Type}, -// {i: mulBoundType, t: Type}, -// {i: {name: "getType", inType: NumDict, outType: Num, fn: getType}, t: Function}, -// {i: {name: "getAdd", inType: NumDict, outType: addType, fn: getAdd }, t: Function}, -// {i: {name: "getMul", inType: NumDict, outType: mulType, fn: getMul }, t: Function}, -// ...makeTypedFnInOut({f: addType , inType: Num, outType: addBoundType}), -// ...makeTypedFnInOut({f: addBoundType, inType: Num, outType: Num }), -// ...makeTypedFnInOut({f: mulType , inType: Num, outType: mulBoundType}), -// ...makeTypedFnInOut({f: mulBoundType, inType: Num, outType: Num }), -// // conformance checking type class -// {i: addType, t: Conformable}, -// {i: mulType, t: Conformable}, -// {i: addIsConform, t: Function}, -// {i: addIsConform, t: conformanceCheck}, -// {i: mulIsConform, t: Function}, -// {i: mulIsConform, t: conformanceCheck}, -// ]; +const IntNumDict = { + add: addInt, + mul: mulInt, +}; + +const DoubleNumDict = { + add: addDouble, + mul: mulDouble, +} + +export const ModuleNumInstances = {l:[ + {i: IntNumDict , t: NumDict}, + {i: DoubleNumDict, t: NumDict}, +]}; + +// 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], +]); \ No newline at end of file diff --git a/typed.js b/typed.js index a7ec80b..0fb5e47 100644 --- a/typed.js +++ b/typed.js @@ -1,4 +1,4 @@ -import { fnType } from "./type_registry.js"; +import { fnType } from "./metacircular.js"; import {Type, Function} from "./metacircular.js"; export const Typed = Symbol('Typed'); diff --git a/util.js b/util.js index fa1aa4f..0a49223 100644 --- a/util.js +++ b/util.js @@ -22,7 +22,6 @@ export function deepEqual(a, b) { return true; } - export class DefaultMap { constructor(defaultValue, ...rest) { this.defaultValue = defaultValue;