feat: Add and illustrate multiple ways of specifying implementations (#19)
Resolves #9. Co-authored-by: Glen Whitney <glen@studioinfinity.org> Reviewed-on: #19
This commit is contained in:
parent
4fdafc751e
commit
d72c443616
@ -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.
|
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).
|
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.
|
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.
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
import {use} from '../core/PocomathInstance.mjs'
|
||||||
export {Types} from './Types/bigint.mjs'
|
export {Types} from './Types/bigint.mjs'
|
||||||
|
|
||||||
export const add = {
|
export const add = {
|
||||||
'...bigint': [[], addends => addends.reduce((x,y) => x+y, 0n)],
|
'...bigint': use([], addends => addends.reduce((x,y) => x+y, 0n))
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import {use} from '../core/PocomathInstance.mjs'
|
||||||
export {Types} from './Types/bigint.mjs'
|
export {Types} from './Types/bigint.mjs'
|
||||||
|
|
||||||
export const negate = {bigint: [[], b => -b ]}
|
export const negate = {bigint: use([], b => -b)}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
export {Types} from './Types/Complex.mjs'
|
export {Types} from './Types/Complex.mjs'
|
||||||
|
|
||||||
export const add = {
|
export const add = {
|
||||||
'...Complex': [['self'], ref => addends => {
|
'...Complex': {
|
||||||
|
uses: ['self'],
|
||||||
|
does: ref => addends => {
|
||||||
if (addends.length === 0) return {re:0, im:0}
|
if (addends.length === 0) return {re:0, im:0}
|
||||||
const seed = addends.shift()
|
const seed = addends.shift()
|
||||||
return addends.reduce((w,z) =>
|
return addends.reduce((w,z) =>
|
||||||
({re: ref.self(w.re, z.re), im: ref.self(w.im, z.im)}), seed)
|
({re: ref.self(w.re, z.re), im: ref.self(w.im, z.im)}), seed)
|
||||||
}]
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ export const complex = {
|
|||||||
* have a numeric/scalar type, e.g. by implementing subtypes in
|
* have a numeric/scalar type, e.g. by implementing subtypes in
|
||||||
* typed-function
|
* 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 */
|
/* Take advantage of conversions in typed-function */
|
||||||
Complex: [[], z => z]
|
Complex: {does: z => z}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
export {Types} from './Types/Complex.mjs'
|
export {Types} from './Types/Complex.mjs'
|
||||||
|
|
||||||
export const negate = {
|
export const negate = {
|
||||||
Complex: [['self'], ref => z => {
|
Complex: {
|
||||||
|
uses: ['self'],
|
||||||
|
does: ref => z => {
|
||||||
return {re: ref.self(z.re), im: ref.self(z.im)}
|
return {re: ref.self(z.re), im: ref.self(z.im)}
|
||||||
}]
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
/* Core of pocomath: create an instance */
|
/* Core of pocomath: create an instance */
|
||||||
import typed from 'typed-function'
|
import typed from 'typed-function'
|
||||||
|
|
||||||
|
export function use(dependencies, implementation) {
|
||||||
|
return [dependencies, implementation]
|
||||||
|
}
|
||||||
|
|
||||||
export default class PocomathInstance {
|
export default class PocomathInstance {
|
||||||
/* Disallowed names for ops; beware, this is slightly non-DRY
|
/* Disallowed names for ops; beware, this is slightly non-DRY
|
||||||
* in that if a new top-level PocomathInstance method is added, its name
|
* in that if a new top-level PocomathInstance method is added, its name
|
||||||
@ -24,15 +28,16 @@ export default class PocomathInstance {
|
|||||||
* @param {Object<string, Object<Signature, [string[], function]>>} ops
|
* @param {Object<string, Object<Signature, [string[], function]>>} ops
|
||||||
* The only parameter ops gives the semantics of the operations to install.
|
* 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
|
* The keys are operation names. The value for a key is an object
|
||||||
* mapping (typed-function) signature strings to pairs of dependency
|
* mapping (typed-function) signature strings to specifications of
|
||||||
* lists and implementation functions.
|
* of dependency lists and implementation functions.
|
||||||
*
|
*
|
||||||
* A dependency list is a list of strings. Each string can either be the
|
* 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,
|
* name of a function that the corresponding implementation has to call,
|
||||||
* or a specification of a particular signature of a function that it has
|
* 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
|
* to call, in the form 'FN(SIGNATURE)' [not implemented yet].
|
||||||
* the special value 'self' to indicate a recursive call to the given
|
* Note the function name can be the special value 'self' to indicate a
|
||||||
* operation (either with or without a particular signature.
|
* recursive call to the given operation (either with or without a
|
||||||
|
* particular signature.
|
||||||
*
|
*
|
||||||
* There are two cases for the implementation function. If the dependency
|
* There are two cases for the implementation function. If the dependency
|
||||||
* list is empty, it should be a function taking the arguments specified
|
* 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
|
* requested functions as values, to a function taking the arguments
|
||||||
* specified by the signature and returning the value.
|
* 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
|
* Note that the "operation" named `Types` is special: it gives
|
||||||
* types that must be installed in the instance. In this case, the keys
|
* 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'
|
* 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) {
|
for (const func in this._imps) {
|
||||||
if (func === 'Types') continue
|
if (func === 'Types') continue
|
||||||
for (const definition of Object.values(this._imps[func])) {
|
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]
|
const depName = dependency.split('(',1)[0]
|
||||||
if (doneSet.has(depName)) continue
|
if (doneSet.has(depName)) continue
|
||||||
requiredSet.add(depName)
|
requiredSet.add(depName)
|
||||||
@ -154,7 +188,20 @@ export default class PocomathInstance {
|
|||||||
this._ensureTypes()
|
this._ensureTypes()
|
||||||
const tf_imps = {}
|
const tf_imps = {}
|
||||||
for (const signature in 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) {
|
if (deps.length === 0) {
|
||||||
tf_imps[signature] = imp
|
tf_imps[signature] = imp
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export {Types} from './Types/number.mjs'
|
export {Types} from './Types/number.mjs'
|
||||||
|
|
||||||
export const add = {
|
export const add = {
|
||||||
'...number': [[], addends => addends.reduce((x,y) => x+y, 0)],
|
'...number': addends => addends.reduce((x,y) => x+y, 0),
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export { Types } from './Types/number.mjs'
|
export { Types } from './Types/number.mjs'
|
||||||
|
|
||||||
export const negate = {number: [[], n => -n]}
|
export const negate = {number: n => -n}
|
||||||
|
Loading…
Reference in New Issue
Block a user