feat: Add all remaining features of original Picomath PoC (#5)
Namely, a README describing the proof-of-concept, a custom selective loader, and some additional tests. Co-authored-by: Glen Whitney <glen@studioinfinity.org> Reviewed-on: #5
This commit is contained in:
parent
84a8b9d5c4
commit
a16d6a5ce3
18
README.md
18
README.md
@ -1,3 +1,21 @@
|
|||||||
# pocomath
|
# pocomath
|
||||||
|
|
||||||
A little proof-of-concept for organizing mathjs by module inclusion, avoiding factory functions.
|
A little proof-of-concept for organizing mathjs by module inclusion, avoiding factory functions.
|
||||||
|
|
||||||
|
Note this project is package-managed by [pnpm](https://pnpm.io/). I do not expect that a clone can easily be manipulated with `npm`.
|
||||||
|
|
||||||
|
Defines a class PocomathInstance to embody independent instances of a mathjs-style CAS. Basically, it keeps track of a collection of implementations (in the sense of typed-function) for each of the functions to be used in the CAS, rather than just the finalized typed-functions. It also tracks the dependencies of each implementation (which must form a directed acyclic network). When a method is requested from the instance, it assembles the proper typed-function (and caches it, of course). Whenever an implementation is added to that function name or any of its dependencies, the previously assembled typed-function is discarded, so that a new one will be constructed on its next use.
|
||||||
|
|
||||||
|
Multiple different instances can coexist and have different collections of operations. Moreover, only the source files for the operations actually desired are ever visited in the import tree, so minimizing a bundle for a specific subset of operations should be quite straightforward.
|
||||||
|
|
||||||
|
Hopefully the test cases, especially `test/_pocomath.mjs` and `test/custom.js`, will show off these aspects in action.
|
||||||
|
|
||||||
|
Note that 'subtract' is implemented as a 'generic' operation, that depends only on the 'add' and 'negate' operations (and so doesn't care what types it is operating on).
|
||||||
|
|
||||||
|
Furthermore, note that 'Complex' is implemented in a way that doesn't care about the types of the real and imaginary components, so with the 'bigint' type defined here as well, we obtain Gaussian integers for free.
|
||||||
|
|
||||||
|
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 conditonal loaders like `complex/extendByComplex.mjs` could be defined.
|
||||||
|
|
||||||
|
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.
|
||||||
|
19
complex/extendToComplex.mjs
Normal file
19
complex/extendToComplex.mjs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import './Complex.mjs'
|
||||||
|
import * as complex from './complex.mjs'
|
||||||
|
|
||||||
|
/* Add all the complex implementations for functions already
|
||||||
|
in the instance:
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default async function extendToComplex(pmath) {
|
||||||
|
pmath.install(complex)
|
||||||
|
for (const name in pmath._imps) {
|
||||||
|
const modulePath = `./${name}.mjs`
|
||||||
|
try {
|
||||||
|
const mod = await import(modulePath)
|
||||||
|
pmath.install(mod)
|
||||||
|
} catch (err) {
|
||||||
|
// Guess it wasn't a method available in complex; no worries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
test/_PocomathInstance.mjs
Normal file
13
test/_PocomathInstance.mjs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import assert from 'assert'
|
||||||
|
import PocomathInstance from '../PocomathInstance.mjs'
|
||||||
|
|
||||||
|
describe('PocomathInstance', () => {
|
||||||
|
it('creates an instance that can define typed-functions', () => {
|
||||||
|
const pi = new PocomathInstance('dummy')
|
||||||
|
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)
|
||||||
|
assert.throws(() => pi.add(1), TypeError)
|
||||||
|
})
|
||||||
|
})
|
@ -3,9 +3,11 @@ import math from '../pocomath.mjs'
|
|||||||
import typed from 'typed-function'
|
import typed from 'typed-function'
|
||||||
import PocomathInstance from '../PocomathInstance.mjs'
|
import PocomathInstance from '../PocomathInstance.mjs'
|
||||||
import * as numbers from '../number/all.mjs'
|
import * as numbers from '../number/all.mjs'
|
||||||
|
import * as numberAdd from '../number/add.mjs'
|
||||||
import * as complex from '../complex/all.mjs'
|
import * as complex from '../complex/all.mjs'
|
||||||
import * as complexAdd from '../complex/add.mjs'
|
import * as complexAdd from '../complex/add.mjs'
|
||||||
import * as complexNegate from '../complex/negate.mjs'
|
import * as complexNegate from '../complex/negate.mjs'
|
||||||
|
import extendToComplex from '../complex/extendToComplex.mjs'
|
||||||
|
|
||||||
const bw = new PocomathInstance('backwards')
|
const bw = new PocomathInstance('backwards')
|
||||||
describe('A custom instance', () => {
|
describe('A custom instance', () => {
|
||||||
@ -38,4 +40,18 @@ describe('A custom instance', () => {
|
|||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pm.subtract({re:5, im:0}, {re:10, im:1}), {re:-5, im: -1})
|
pm.subtract({re:5, im:0}, {re:10, im:1}), {re:-5, im: -1})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("can selectively import in cute ways", async function () {
|
||||||
|
const cherry = new PocomathInstance('cherry')
|
||||||
|
cherry.install(numberAdd)
|
||||||
|
await extendToComplex(cherry)
|
||||||
|
// Now we have an instance that supports addition for number and complex
|
||||||
|
// and little else:
|
||||||
|
assert.strictEqual(cherry.add(3, 4, 2), 9)
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
cherry.add(cherry.complex(3, 3), 4, cherry.complex(2, 2)),
|
||||||
|
math.complex(9,5))
|
||||||
|
assert.strictEqual('subtract' in cherry, false)
|
||||||
|
assert.strictEqual('negate' in cherry, false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user