diff --git a/README.md b/README.md index 1e13a6e..4c6ab82 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ Furthermore, note that 'Complex' is implemented in a way that doesn't care about This core could be extended with many more operations, and more types could be defined, and additional sub-bundles like `number/all.mjs` or clever conditional loaders like `complex/extendToComplex.mjs` could be defined. +Also see the comments for the public member functions of +`core/PocomathInstance.mjs` for further details on the structure and API of this +scheme for organizing a CAS. + Hopefully this shows promise. It is an evolution of the concept first prototyped in [picomath](https://code.studioinfinity.org/glen/picomath). However, picomath depended on typed-function allowing mutable function entities, which turned out not to be performant. Pocomath, on the other hand, uses typed-function v3 as it stands, although it does suggest that it would be helpful to extend typed-function with subtypes, and it could even be reasonable to move the dependency tracking into typed-function itself (given that typed-function already supports self-dependencies, it would not be difficult to extend that to inter-dependencies between different typed-functions). Note the conception of Pocomath includes allowing one implementation to depend just on a specific signature of another function, for efficiency's sake (if for example 'bar(Matrix)' knows it will only call 'foo(Matrix)', it avoids another type-dispatch). That capability did not actually come up in this toy example, so it remains unimplemented, but it should and could easily be added. diff --git a/src/bigint/add.mjs b/src/bigint/add.mjs index d6fc0c7..dfff057 100644 --- a/src/bigint/add.mjs +++ b/src/bigint/add.mjs @@ -1,5 +1,6 @@ +import {use} from '../core/PocomathInstance.mjs' export {Types} from './Types/bigint.mjs' export const add = { - '...bigint': [[], addends => addends.reduce((x,y) => x+y, 0n)], + '...bigint': use([], addends => addends.reduce((x,y) => x+y, 0n)) } diff --git a/src/bigint/negate.mjs b/src/bigint/negate.mjs index 538e1d4..eed50bf 100644 --- a/src/bigint/negate.mjs +++ b/src/bigint/negate.mjs @@ -1,3 +1,4 @@ +import {use} from '../core/PocomathInstance.mjs' export {Types} from './Types/bigint.mjs' -export const negate = {bigint: [[], b => -b ]} +export const negate = {bigint: use([], b => -b)} diff --git a/src/complex/add.mjs b/src/complex/add.mjs index 3583b38..d44d3fd 100644 --- a/src/complex/add.mjs +++ b/src/complex/add.mjs @@ -1,10 +1,13 @@ export {Types} from './Types/Complex.mjs' export const add = { - '...Complex': [['self'], ref => addends => { - if (addends.length === 0) return {re:0, im:0} - const seed = addends.shift() - return addends.reduce((w,z) => - ({re: ref.self(w.re, z.re), im: ref.self(w.im, z.im)}), seed) - }] + '...Complex': { + uses: ['self'], + does: ref => addends => { + if (addends.length === 0) return {re:0, im:0} + const seed = addends.shift() + return addends.reduce((w,z) => + ({re: ref.self(w.re, z.re), im: ref.self(w.im, z.im)}), seed) + } + } } diff --git a/src/complex/complex.mjs b/src/complex/complex.mjs index c86c3e8..9a5cee9 100644 --- a/src/complex/complex.mjs +++ b/src/complex/complex.mjs @@ -5,7 +5,7 @@ export const complex = { * have a numeric/scalar type, e.g. by implementing subtypes in * typed-function */ - 'any, any': [[], (x, y) => ({re: x, im: y})], + 'any, any': {does: (x, y) => ({re: x, im: y})}, /* Take advantage of conversions in typed-function */ - Complex: [[], z => z] + Complex: {does: z => z} } diff --git a/src/complex/negate.mjs b/src/complex/negate.mjs index bf4e612..9cb21c6 100644 --- a/src/complex/negate.mjs +++ b/src/complex/negate.mjs @@ -1,7 +1,10 @@ export {Types} from './Types/Complex.mjs' export const negate = { - Complex: [['self'], ref => z => { - return {re: ref.self(z.re), im: ref.self(z.im)} - }] + Complex: { + uses: ['self'], + does: ref => z => { + return {re: ref.self(z.re), im: ref.self(z.im)} + } + } } diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index 4d32896..4fd2584 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -1,6 +1,10 @@ /* Core of pocomath: create an instance */ import typed from 'typed-function' +export function use(dependencies, implementation) { + return [dependencies, implementation] +} + export default class PocomathInstance { /* Disallowed names for ops; beware, this is slightly non-DRY * in that if a new top-level PocomathInstance method is added, its name @@ -24,15 +28,16 @@ export default class PocomathInstance { * @param {Object>} ops * The only parameter ops gives the semantics of the operations to install. * The keys are operation names. The value for a key is an object - * mapping (typed-function) signature strings to pairs of dependency - * lists and implementation functions. + * mapping (typed-function) signature strings to specifications of + * of dependency lists and implementation functions. * * A dependency list is a list of strings. Each string can either be the * name of a function that the corresponding implementation has to call, * or a specification of a particular signature of a function that it has - * to call, in the form 'FN(SIGNATURE)'. Note the function name can be - * the special value 'self' to indicate a recursive call to the given - * operation (either with or without a particular signature. + * to call, in the form 'FN(SIGNATURE)' [not implemented yet]. + * Note the function name can be the special value 'self' to indicate a + * recursive call to the given operation (either with or without a + * particular signature. * * There are two cases for the implementation function. If the dependency * list is empty, it should be a function taking the arguments specified @@ -41,6 +46,30 @@ export default class PocomathInstance { * requested functions as values, to a function taking the arguments * specified by the signature and returning the value. * + * There are various specifications currently allowed for the + * dependency list and implementation function: + * + * 1) Just a function. Then the dependency list is assumed to be empty. + * + * 2) A pair (= Array with two entries) of a dependency list and the + * implementation function. + * + * 3) An object whose property named 'does' gives the implementation + * function and whose property named 'uses', if present, gives the + * dependency list (which is assumed to be empty if the property is + * not present). + * + * 4) A call to the 'use' function exported from the this module, with + * first argument the dependencies and second argument the + * implementation. + * + * For a visual comparison of the options, this proof-of-concept uses + * option (1) when possible for the 'number' type, (3) for the 'Complex' + * type, (4) for the 'bigint' type, and (2) under any other circumstances. + * Likely a fleshed-out version of this scheme would settle on just one + * or two of these options or variants thereof, rather than providing so + * many different ones. + * * Note that the "operation" named `Types` is special: it gives * types that must be installed in the instance. In this case, the keys * are type names, and the values are objects with a property 'test' @@ -66,7 +95,12 @@ export default class PocomathInstance { for (const func in this._imps) { if (func === 'Types') continue for (const definition of Object.values(this._imps[func])) { - for (const dependency of definition[0]) { + let deps = [] + if (Array.isArray(definition)) deps = definition[0] + else if (typeof definition === 'object') { + deps = definition.uses || deps + } + for (const dependency of deps) { const depName = dependency.split('(',1)[0] if (doneSet.has(depName)) continue requiredSet.add(depName) @@ -154,7 +188,20 @@ export default class PocomathInstance { this._ensureTypes() const tf_imps = {} for (const signature in imps) { - const [deps, imp] = imps[signature] + const specifier = imps[signature] + let deps = [] + let imp + if (typeof specifier === 'function') { + imp = specifier + } else if (Array.isArray(specifier)) { + [deps, imp] = specifier + } else if (typeof specifier === 'object') { + deps = specifier.uses || deps + imp = specifier.does + } else { + throw new SyntaxError( + `Cannot interpret signature definition ${specifier}`) + } if (deps.length === 0) { tf_imps[signature] = imp } else { diff --git a/src/number/add.mjs b/src/number/add.mjs index 70be9bb..d47b10e 100644 --- a/src/number/add.mjs +++ b/src/number/add.mjs @@ -1,5 +1,5 @@ export {Types} from './Types/number.mjs' export const add = { - '...number': [[], addends => addends.reduce((x,y) => x+y, 0)], + '...number': addends => addends.reduce((x,y) => x+y, 0), } diff --git a/src/number/negate.mjs b/src/number/negate.mjs index aaf8bc0..bdfa823 100644 --- a/src/number/negate.mjs +++ b/src/number/negate.mjs @@ -1,3 +1,3 @@ export { Types } from './Types/number.mjs' -export const negate = {number: [[], n => -n]} +export const negate = {number: n => -n}