feat: Initial core of picomath

Implements a totally simplistic "poortf" mutable typed function and
  a picomath instance generator, as well as the very beginnings of a
  number type and one generic function and a default full picomath instance.

  Also provides some tests which serve as usage examples.
This commit is contained in:
Glen Whitney 2022-03-25 00:49:03 -07:00
parent 36cc91ca95
commit 536656bfe8
14 changed files with 1627 additions and 2 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
# Editor backups
*~
# Logs # Logs
logs logs
*.log *.log

9
generic/subtract.js Normal file
View File

@ -0,0 +1,9 @@
export default function create(pmath) {
const add = pmath('add')
const negate = pmath('negate')
if (!pmath.subtract) { // avoid double definition at cost of extensibility
pmath('subtract', [args => args.length === 2,
(x, y) => add(x, negate(y))])
}
return pmath.subtract
}

6
number/add.js Normal file
View File

@ -0,0 +1,6 @@
import { allNumbers } from './number.js'
export default function create(pmath) {
return pmath('add', [allNumbers,
(...addends) => addends.reduce((x,y) => x+y, 0)])
}

12
number/all.js Normal file
View File

@ -0,0 +1,12 @@
import createNumber from './number.js'
import createAdd from './add.js'
import createNegate from './negate.js'
import createSubtract from '../generic/subtract.js'
export default function create(pmath) {
createNumber(pmath)
createAdd(pmath)
createNegate(pmath)
createSubtract(pmath)
// not sure if there's anything reasonable to return here
}

7
number/negate.js Normal file
View File

@ -0,0 +1,7 @@
import { oneNumber } from './number.js'
export default function create(pmath) {
return pmath('negate', [oneNumber, n => -n])
}

14
number/number.js Normal file
View File

@ -0,0 +1,14 @@
export function allNumbers(args) {
for (let i = 0; i < args.length; ++i) {
if (typeof args[i] !== 'number') return false
}
return true
}
export function oneNumber(args) {
return args.length === 1 && typeof args[0] === 'number'
}
export default function create(pmath) {
return pmath('number', [() => true, x => Number(x)])
}

1446
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"description": "Proof-of-concept tiny package like mathjs with mutable \"typed functions\" and module-based dependency tracking and \"tree-shaking\"", "description": "Proof-of-concept tiny package like mathjs with mutable \"typed functions\" and module-based dependency tracking and \"tree-shaking\"",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "mocha" "test": "npx mocha"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -15,5 +15,9 @@
"algebra" "algebra"
], ],
"author": "Glen Whitney", "author": "Glen Whitney",
"license": "Apache-2.0" "license": "Apache-2.0",
"type": "module",
"dependencies": {
"mocha": "^9.2.2"
}
} }

8
picomath.js Normal file
View File

@ -0,0 +1,8 @@
/* Core of picomath: generates an instance */
import picomathInstance from './picomathInstance.js'
import createNumbers from './number/all.js'
const math = picomathInstance('math')
createNumbers(math)
export default math

20
picomathInstance.js Normal file
View File

@ -0,0 +1,20 @@
/* Core of picomath: generates an instance */
import poortf from './poortf.js'
export default function picomathInstance (instName) {
function fn (name, imps) {
if (name in fn) {
fn[name].addImps(imps)
} else {
fn[name] = poortf(name, imps)
}
return fn[name]
}
Object.defineProperty(fn, 'name', {value: instName})
return fn
}

27
poortf.js Normal file
View File

@ -0,0 +1,27 @@
/* Totally minimal "typed functions" */
const addImps = (dest, imps) => {
if (imps) {
if (!Array.isArray(imps[0])) imps = [imps]
for (const imp of imps) dest.push(imp)
}
}
export default function poortf (name, imps) {
/* This is the (function) object we will return */
function fn () {
for (const imp of fn.imps) {
if (imp[0](arguments)) return imp[1].apply(null, arguments)
}
throw new TypeError(
`TF ${fn.name}: No match for ${arguments[0]}, ${arguments[1]}, ...`)
}
/* Now dress it up for use */
Object.defineProperty(fn, 'name', {value: name})
fn.imps = []
addImps(fn.imps, imps)
fn.addImps = newI => addImps(fn.imps, newI)
return fn
}

15
test/_picomath.js Normal file
View File

@ -0,0 +1,15 @@
import assert from 'assert'
import math from '../picomath.js'
describe('The default full picomath instance "math"', () => {
it('performs basic arithmetic operations', () => {
assert.strictEqual(math.subtract(16, math.add(3,4,2)), 7)
assert.strictEqual(math.negate(math.number('8')), -8)
})
it('can be extended', () => {
math('add', [args => typeof args[0] === 'string',
(...addends) => addends.reduce((x,y) => x+y, '')])
assert.strictEqual(math.add('Kilroy',' is here'), 'Kilroy is here')
})
})

13
test/_picomathInstance.js Normal file
View File

@ -0,0 +1,13 @@
import assert from 'assert'
import picomathInstance from '../picomathInstance.js'
describe('picomath core', () => {
it('creates an instance that can define TFs', () => {
const pmath = picomathInstance('pmath')
pmath('add', [() => true, (a,b) => a+b])
assert.strictEqual(pmath.add(2,2), 4)
assert.strictEqual(pmath.add('Kilroy', 17), 'Kilroy17')
assert.strictEqual(pmath.add(1), NaN)
// I guess + never throws!
})
})

41
test/_poortf.js Normal file
View File

@ -0,0 +1,41 @@
import assert from 'assert'
import poortf from '../poortf.js'
describe('poortf', () => {
const slate = poortf('slate')
it('creates an empty tf', () => {
assert.throws(() => slate('empty'), TypeError)
assert.throws(() => slate('empty'), /slate.*empty/)
})
const add = poortf('add', [
[args => Array.from(args).every(a => typeof a === 'number'),
(a, b) => a+b],
[args => Array.from(args).every(a => typeof a === 'boolean'),
(p, q) => p || q]
])
it('creates a tf with initial behaviors', () => {
assert.strictEqual(add(2,2), 4)
assert.strictEqual(add(true, false), true)
assert.throws(() => add('kilroy'), TypeError)
})
it('extends an empty tf', () => {
slate.addImps([
[args => typeof args[0] === 'string', s => s + ' wuz here'],
[args => typeof args[0] === 'number', () => 'I am not a number']
])
assert.strictEqual(slate('Kilroy', 'was here'), 'Kilroy wuz here')
assert.strictEqual(slate(2, 'was here'), 'I am not a number')
assert.throws(() => slate(['Ha!']), TypeError)
})
it('extends a tf with other behaviors', () => {
add.addImps([args => typeof args[0] === 'string', (s,x) => s + x]),
assert.strictEqual(add('Kilroy', 23), 'Kilroy23')
assert.throws(() => add(['Ha!'], 'gotcha'), TypeError)
})
})