diff --git a/src/bigint/add.mjs b/src/bigint/add.mjs index dfff057..cc1f48e 100644 --- a/src/bigint/add.mjs +++ b/src/bigint/add.mjs @@ -1,6 +1,5 @@ -import {use} from '../core/PocomathInstance.mjs' export {Types} from './Types/bigint.mjs' export const add = { - '...bigint': use([], addends => addends.reduce((x,y) => x+y, 0n)) + '...bigint': () => addends => addends.reduce((x,y) => x+y, 0n) } diff --git a/src/bigint/negate.mjs b/src/bigint/negate.mjs index eed50bf..874a377 100644 --- a/src/bigint/negate.mjs +++ b/src/bigint/negate.mjs @@ -1,4 +1,3 @@ -import {use} from '../core/PocomathInstance.mjs' export {Types} from './Types/bigint.mjs' -export const negate = {bigint: use([], b => -b)} +export const negate = {bigint: () => b => -b} diff --git a/src/complex/add.mjs b/src/complex/add.mjs index d44d3fd..27374b6 100644 --- a/src/complex/add.mjs +++ b/src/complex/add.mjs @@ -1,13 +1,10 @@ export {Types} from './Types/Complex.mjs' export const add = { - '...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) - } + '...Complex': ({self}) => addends => { + if (addends.length === 0) return {re:0, im:0} + const seed = addends.shift() + return addends.reduce( + (w,z) => ({re: self(w.re, z.re), im: self(w.im, z.im)}), seed) } } diff --git a/src/complex/complex.mjs b/src/complex/complex.mjs index 9a5cee9..5cbbced 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': {does: (x, y) => ({re: x, im: y})}, + 'any, any': () => (x, y) => ({re: x, im: y}), /* Take advantage of conversions in typed-function */ - Complex: {does: z => z} + Complex: () => z => z } diff --git a/src/complex/negate.mjs b/src/complex/negate.mjs index 9cb21c6..45b9840 100644 --- a/src/complex/negate.mjs +++ b/src/complex/negate.mjs @@ -1,10 +1,5 @@ export {Types} from './Types/Complex.mjs' export const negate = { - Complex: { - uses: ['self'], - does: ref => z => { - return {re: ref.self(z.re), im: ref.self(z.im)} - } - } + Complex: ({self}) => z => ({re: self(z.re), im: self(z.im)}) } diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index 4fd2584..83a11f8 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -1,9 +1,6 @@ /* Core of pocomath: create an instance */ import typed from 'typed-function' - -export function use(dependencies, implementation) { - return [dependencies, implementation] -} +import dependencyExtractor from './dependencyExtractor.mjs' export default class PocomathInstance { /* Disallowed names for ops; beware, this is slightly non-DRY @@ -25,50 +22,35 @@ export default class PocomathInstance { /** * (Partially) define one or more operations of the instance: * - * @param {Object>} ops + * @param {Object implementation>>} 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 specifications of - * of dependency lists and implementation functions. + * mapping each desired (typed-function) signature to a function taking + * a dependency object to an implementation. * - * 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)' [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. + * For more detail, such functions should have the format + * ``` + * ({depA, depB, depC: aliasC, ...}) => (opArg1, opArg2) => + * ``` + * where the `depA`, `depB` etc. are the names of the + * operations this implementation depends on; those operations can + * then be referred to directly by the identifiers `depA` and `depB` + * in the code for the '`, or when an alias has been given + * as in the case of `depC`, by the identifier `aliasC`. + * Given an object that has these dependencies with these keys, the + * function returns a function taking the operation arguments to the + * desired result of the operation. * - * There are two cases for the implementation function. If the dependency - * list is empty, it should be a function taking the arguments specified - * by the signature and returning the value. Otherwise, it should be - * a function taking an object with the dependency lists as keys and the - * requested functions as values, to a function taking the arguments - * specified by the signature and returning the value. + * You can specify that an operation depends on itself by using the + * special dependency identifier 'self'. * - * 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. + * You can specify that an implementation depends on just a specific + * signature of the given operation by suffixing the dependency name + * with the signature in parentheses, e.g. `add(number,number)` to + * refer to just adding two numbers. In this case, it is of course + * necessary to specify an alias to be able to refer to the supplied + * operation in the body of the implementation. [NOTE: this signature- + * specific reference is not yet implemented.] * * Note that the "operation" named `Types` is special: it gives * types that must be installed in the instance. In this case, the keys @@ -94,13 +76,8 @@ export default class PocomathInstance { /* Grab all of the known deps */ for (const func in this._imps) { if (func === 'Types') continue - for (const definition of Object.values(this._imps[func])) { - let deps = [] - if (Array.isArray(definition)) deps = definition[0] - else if (typeof definition === 'object') { - deps = definition.uses || deps - } - for (const dependency of deps) { + for (const {uses} of Object.values(this._imps[func])) { + for (const dependency of uses) { const depName = dependency.split('(',1)[0] if (doneSet.has(depName)) continue requiredSet.add(depName) @@ -137,14 +114,32 @@ export default class PocomathInstance { // new implementations, so set the op up to lazily recreate itself this._invalidate(name) const opImps = this._imps[name] - for (const signature in implementations) { + for (const [signature, does] of Object.entries(implementations)) { + if (name === 'Types') { + if (signature in opImps) { + if (does != opImps[signature]) { + throw newSyntaxError( + `Conflicting definitions of type ${signature}`) + } + } else { + opImps[signature] = does + } + continue + } if (signature in opImps) { - if (implementations[signature] === opImps[signature]) continue - throw new SyntaxError( - `Conflicting definitions of ${signature} for ${name}`) + if (does !== opImps[signature].does) { + throw new SyntaxError( + `Conflicting definitions of ${signature} for ${name}`) + } } else { - opImps[signature] = implementations[signature] - for (const dep of implementations[signature][0] || []) { + if (name === 'Types') { + opImps[signature] = does + continue + } + const uses = new Set() + does(dependencyExtractor(uses)) + opImps[signature] = {uses, does} + for (const dep of uses) { const depname = dep.split('(', 1)[0] if (depname === 'self') continue if (!(depname in this._affects)) { @@ -187,27 +182,13 @@ export default class PocomathInstance { } this._ensureTypes() const tf_imps = {} - for (const signature in imps) { - 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 + for (const [signature, {uses, does}] of Object.entries(imps)) { + if (uses.length === 0) { + tf_imps[signature] = does() } else { const refs = {} let self_referential = false - for (const dep of deps) { + for (const dep of uses) { // TODO: handle signature-specific dependencies if (dep.includes('(')) { throw new Error('signature specific reference unimplemented') @@ -221,10 +202,10 @@ export default class PocomathInstance { if (self_referential) { tf_imps[signature] = this._typed.referToSelf(self => { refs.self = self - return imp(refs) + return does(refs) }) } else { - tf_imps[signature] = imp(refs) + tf_imps[signature] = does(refs) } } } diff --git a/src/core/dependencyExtractor.mjs b/src/core/dependencyExtractor.mjs new file mode 100644 index 0000000..fbf5611 --- /dev/null +++ b/src/core/dependencyExtractor.mjs @@ -0,0 +1,9 @@ +/* Call this with an empty Set object S, and it returns an entity E + * from which properties can be extracted, and at any time S will + * contain all of the property names that have been extracted from E. + */ +export default function dependencyExtractor(destinationSet) { + return new Proxy({}, { + get: (target, property) => { destinationSet.add(property) } + }) +} diff --git a/src/generic/subtract.mjs b/src/generic/subtract.mjs index 8a90697..96a27bf 100644 --- a/src/generic/subtract.mjs +++ b/src/generic/subtract.mjs @@ -1,3 +1,3 @@ export const subtract = { - 'any,any': [['add', 'negate'], ref => (x,y) => ref.add(x, ref.negate(y))] -} + 'any,any': ({add, negate}) => (x,y) => add(x, negate(y)) +} diff --git a/src/number/add.mjs b/src/number/add.mjs index d47b10e..e473c50 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 bdfa823..387d905 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} diff --git a/test/_pocomath.mjs b/test/_pocomath.mjs index 507d811..1ede574 100644 --- a/test/_pocomath.mjs +++ b/test/_pocomath.mjs @@ -19,7 +19,7 @@ describe('The default full pocomath instance "math"', () => { it('can be extended', () => { math.install({'add': { - '...string': [[], addends => addends.reduce((x,y) => x+y, '')] + '...string': () => addends => addends.reduce((x,y) => x+y, '') }}) assert.strictEqual(math.add('Kilroy',' is here'), 'Kilroy is here') }) diff --git a/test/core/_PocomathInstance.mjs b/test/core/_PocomathInstance.mjs index 5f64774..10cbb7a 100644 --- a/test/core/_PocomathInstance.mjs +++ b/test/core/_PocomathInstance.mjs @@ -4,7 +4,7 @@ import PocomathInstance from '../../src/core/PocomathInstance.mjs' const pi = new PocomathInstance('dummy') describe('PocomathInstance', () => { it('creates an instance that can define typed-functions', () => { - pi.install({add: {'any,any': [[], (a,b) => a+b]}}) + pi.install({add: {'any,any': () => (a,b) => a+b}}) assert.strictEqual(pi.add(2,2), 4) assert.strictEqual(pi.add('Kilroy', 17), 'Kilroy17') assert.strictEqual(pi.add(1, undefined), NaN) diff --git a/test/core/_dependencyExtractor.mjs b/test/core/_dependencyExtractor.mjs new file mode 100644 index 0000000..91e0e40 --- /dev/null +++ b/test/core/_dependencyExtractor.mjs @@ -0,0 +1,22 @@ +import assert from 'assert' +import dependencyExtractor from '../../src/core/dependencyExtractor.mjs' + +describe('dependencyExtractor', () => { + it('will record the keys of a destructuring function', () => { + const myfunc = ({a, 'b(x)': b, c: alias}) => 0 + const params = new Set() + myfunc(dependencyExtractor(params)) + assert.ok(params.has('a')) + assert.ok(params.has('b(x)')) + assert.ok(params.has('c')) + assert.ok(params.size === 3) + }) + + it('does not pick up anything from a regular function', () => { + const myfunc = arg => 0 + const params = new Set() + myfunc(dependencyExtractor(params)) + assert.ok(params.size === 0) + }) + +})