Adds all of the pocomath functions on Complex that do not depend on any unimplemented types or config properties, except quotient and roundquotient, where the design is murky. To get this working, adds some additional features: * Allows conversions to generic types, with the matched type determined from the return value of the built convertor * Adds predicate-based type patterns * Adds conversion from any non-complex type T to Complex(T) * Adds check for recursive loops in resolve (a key/signature depending on itself) Reviewed-on: #24 Co-authored-by: Glen Whitney <glen@studioinfinity.org> Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
parent
0ff00ff8cb
commit
aad62df8ac
33 changed files with 428 additions and 106 deletions
|
@ -1,6 +1,7 @@
|
|||
import {Type} from '#core/Type.js'
|
||||
|
||||
export const BooleanT = new Type(n => typeof n === 'boolean', {
|
||||
typeName: 'BooleanT',
|
||||
zero: false,
|
||||
one: true
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {BooleanT} from './BooleanT.js'
|
||||
import {match} from '#core/helpers.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Type} from '#core/Type.js'
|
||||
import {match} from '#core/helpers.js'
|
||||
import {Returns, Type} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
|
||||
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z
|
||||
|
||||
|
@ -66,6 +66,12 @@ function complexSpecialize(ComponentType) {
|
|||
export const Complex = new Type(isComplex, {
|
||||
specialize: complexSpecialize,
|
||||
specializesTo,
|
||||
from: [match( // can promote any non-complex type T to Complex(T) as needed
|
||||
// but watch out, this should be tried late, because it can preclude
|
||||
// other more reasonable conversions like bool => number.
|
||||
T => !T.complex,
|
||||
(math, T) => Returns(Complex(T), r => math.complex(r, math.zero(T)))
|
||||
)],
|
||||
refine: function(z, typer) {
|
||||
const reType = typer(z.re)
|
||||
const imType = typer(z.im)
|
||||
|
|
78
src/complex/__test__/arithmetic.spec.js
Normal file
78
src/complex/__test__/arithmetic.spec.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {Complex} from '../Complex.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
const cplx = math.complex
|
||||
|
||||
describe('complex arithmetic operations', () => {
|
||||
it('computes absquare of complex numbers', () => {
|
||||
assert.strictEqual(math.absquare(cplx(3, 4)), 25)
|
||||
assert.strictEqual(math.absquare(cplx(cplx(2, 3), cplx(4,5))), 54)
|
||||
assert.strictEqual(math.absquare(cplx(true, true)), 2)
|
||||
})
|
||||
it('adds complex numbers', () => {
|
||||
const z = cplx(3, 4)
|
||||
const add = math.add
|
||||
assert.deepStrictEqual(add(z, cplx(-1, 1)), cplx(2, 5))
|
||||
assert.deepStrictEqual(add(z, cplx(true, false)), cplx(4, 4))
|
||||
assert.deepStrictEqual(add(cplx(false, true), z), cplx(3, 5))
|
||||
assert.deepStrictEqual(
|
||||
add(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
|
||||
cplx(cplx(3.5, 4.5), cplx(6, 8)))
|
||||
assert.deepStrictEqual(add(z, 5), cplx(8, 4))
|
||||
assert.deepStrictEqual(add(true, z), cplx(4, 4))
|
||||
assert.deepStrictEqual(add(cplx(z,z), 10), cplx(cplx(13, 4), z))
|
||||
assert.deepStrictEqual(
|
||||
add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4)))
|
||||
})
|
||||
it('conjugates complex numbers', () => {
|
||||
const conj = math.conj
|
||||
const z = cplx(3, 4)
|
||||
assert.deepStrictEqual(conj(z), cplx(3, -4))
|
||||
assert.deepStrictEqual(conj(cplx(z,z)), cplx(cplx(3, -4), cplx(-3, -4)))
|
||||
})
|
||||
it('divides complex numbers', () => {
|
||||
const div = math.divide
|
||||
const z = cplx(3, 4)
|
||||
assert.deepStrictEqual(div(z, cplx(0, 1)), cplx(4, -3))
|
||||
assert(math.equal(div(z, z), 1))
|
||||
// should probably have a quaternion example, but it's intricate
|
||||
})
|
||||
it('inverts complex numbers', () => {
|
||||
const inv = math.invert
|
||||
assert.deepStrictEqual(inv(cplx(0, 1)), cplx(0, -1))
|
||||
assert.deepStrictEqual(inv(cplx(3, 4)), cplx(3/25, -4/25))
|
||||
assert.deepStrictEqual(
|
||||
inv(cplx(cplx(1, 2), cplx(4, 2))),
|
||||
cplx(cplx(1/25, -2/25), cplx(-4/25, -2/25)))
|
||||
})
|
||||
it('multiplies complex numbers', () => {
|
||||
const mult = math.multiply
|
||||
const z = cplx(3, 4)
|
||||
assert.deepStrictEqual(mult(z, z), cplx(-7, 24))
|
||||
assert(math.equal(mult(z, math.conj(z)), 25))
|
||||
const q0 = cplx(cplx(1, 1), math.zero(Complex(NumberT)))
|
||||
const q1 = cplx(cplx(1, 0.5), cplx(0.5, 0.75))
|
||||
assert.deepStrictEqual(
|
||||
mult(q0, q1), cplx(cplx(0.5, 1.5), cplx(1.25, 0.25)))
|
||||
assert.deepStrictEqual(
|
||||
mult(q0, cplx(cplx(2, 0.1), cplx(1, 0.1))),
|
||||
cplx(cplx(1.9, 2.1), cplx(1.1, -0.9)))
|
||||
})
|
||||
it('subtracts complex numbers', () => {
|
||||
const z = cplx(3, 4)
|
||||
const sub = math.subtract
|
||||
assert.deepStrictEqual(sub(z, cplx(-1, 1)), cplx(4, 3))
|
||||
assert.deepStrictEqual(sub(z, cplx(true, false)), cplx(2, 4))
|
||||
assert.deepStrictEqual(sub(cplx(false, true), z), cplx(-3, -3))
|
||||
assert.deepStrictEqual(
|
||||
sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
|
||||
cplx(cplx(2.5, 3.5), cplx(0, 0)))
|
||||
assert.deepStrictEqual(sub(z, 5), cplx(-2, 4))
|
||||
assert.deepStrictEqual(sub(true, z), cplx(-2, -4))
|
||||
assert.deepStrictEqual(sub(cplx(z,z), 10), cplx(cplx(-7, 4), z))
|
||||
assert.deepStrictEqual(
|
||||
sub(cplx(z,z), cplx(10,20)), cplx(cplx(-7, 4), cplx(-17, 4)))
|
||||
})
|
||||
})
|
|
@ -1,12 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
|
||||
describe('complex type operations', () => {
|
||||
it('converts to number', () => {
|
||||
assert.deepStrictEqual(math.complex(3), {re: 3, im: 0})
|
||||
assert.deepStrictEqual(math.complex(NaN), {re: NaN, im: NaN})
|
||||
assert.deepStrictEqual(math.complex(3, -1), {re: 3, im: -1})
|
||||
assert.deepStrictEqual(math.complex(false, true), {re: false, im: true})
|
||||
assert.throws(() => math.complex(3, false), RangeError)
|
||||
})
|
||||
})
|
39
src/complex/__test__/type.spec.js
Normal file
39
src/complex/__test__/type.spec.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
|
||||
const cplx = math.complex
|
||||
|
||||
describe('complex type operations', () => {
|
||||
it('converts to number', () => {
|
||||
assert.deepStrictEqual(cplx(3), {re: 3, im: 0})
|
||||
assert.deepStrictEqual(cplx(NaN), {re: NaN, im: NaN})
|
||||
assert.deepStrictEqual(cplx(3, -1), {re: 3, im: -1})
|
||||
assert.deepStrictEqual(cplx(false, true), {re: false, im: true})
|
||||
assert.throws(() => cplx(3, false), RangeError)
|
||||
})
|
||||
it('calculates the argument of a complex number', () => {
|
||||
assert.strictEqual(math.arg(cplx(1, Math.sqrt(3))), Math.PI/3)
|
||||
assert.strictEqual(math.arg(cplx(true, true)), Math.PI/4)
|
||||
})
|
||||
it('detects associates of a complex number', () => {
|
||||
const z = cplx(3, 4)
|
||||
const assoc = math.associate
|
||||
assert(assoc(z, z))
|
||||
assert(assoc(z, cplx(-3, -4)))
|
||||
assert(assoc(z, cplx(-4, 3)))
|
||||
assert(assoc(z, cplx(4, -3)))
|
||||
assert(!assoc(z, math.conj(z)))
|
||||
const b = cplx(true, true)
|
||||
assert(assoc(b, cplx(-1, 1)))
|
||||
assert(assoc(cplx(1, 1), b))
|
||||
assert(assoc(cplx(-1, -1), b))
|
||||
assert(assoc(cplx(1, -1), b))
|
||||
assert(assoc(cplx(-1, 1), b))
|
||||
assert(!assoc(b, cplx(false, true)))
|
||||
assert(!assoc(cplx(0, 1), b))
|
||||
})
|
||||
it('computes cis of an angle', () => {
|
||||
assert(math.equal(math.cis(0), 1))
|
||||
assert(math.equal(math.cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2)))
|
||||
})
|
||||
})
|
|
@ -1,2 +1,5 @@
|
|||
export * as typeDefinition from './Complex.js'
|
||||
export * as arithmetic from './arithmetic.js'
|
||||
export * as relational from './relational.js'
|
||||
export * as type from './type.js'
|
||||
export * as utilities from './utils.js'
|
||||
|
|
69
src/complex/arithmetic.js
Normal file
69
src/complex/arithmetic.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {promoteBinary, promoteUnary} from './helpers.js'
|
||||
import {ResolutionError} from '#core/helpers.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
export const absquare = match(Complex, (math, C) => {
|
||||
const compAbsq = math.absquare.resolve([C.Component])
|
||||
const R = compAbsq.returns
|
||||
const add = math.add.resolve([R,R])
|
||||
return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im)))
|
||||
})
|
||||
|
||||
export const add = promoteBinary('add')
|
||||
|
||||
export const conj = match(Complex, (math, C) => {
|
||||
const neg = math.negate.resolve(C.Component)
|
||||
const compConj = math.conj.resolve(C.Component)
|
||||
const cplx = math.complex.resolve([compConj.returns, neg.returns])
|
||||
return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im)))
|
||||
})
|
||||
|
||||
export const divide = [
|
||||
match([Complex, T => !T.complex], (math, [C, R]) => {
|
||||
const div = math.divide.resolve([C.Component, R])
|
||||
const cplx = math.complex.resolve([div.returns, div.returns])
|
||||
return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r)))
|
||||
}),
|
||||
match([Complex, Complex], (math, [W, Z]) => {
|
||||
const inv = math.invert.resolve(Z)
|
||||
const mult = math.multiply.resolve([W, inv.returns])
|
||||
return ReturnsAs(mult, (w, z) => mult(w, inv(z)))
|
||||
})
|
||||
]
|
||||
|
||||
export const invert = match(Complex, (math, C) => {
|
||||
const conj = math.conj.resolve(C)
|
||||
const norm = math.absquare.resolve(C)
|
||||
const div = math.divide.resolve([C.Component, norm.returns])
|
||||
const cplx = math.complex.resolve([div.returns, div.returns])
|
||||
return ReturnsAs(cplx, z => {
|
||||
const c = conj(z)
|
||||
const d = norm(z)
|
||||
return cplx(div(c.re, d), div(c.im, d))
|
||||
})
|
||||
})
|
||||
|
||||
// We want this to work for complex numbers, quaternions, octonions, etc
|
||||
// See https://math.ucr.edu/home/baez/octonions/node5.html
|
||||
export const multiply = match([Complex, Complex], (math, [W, Z]) => {
|
||||
const conj = math.conj.resolve(W.Component)
|
||||
if (conj.returns !== W.Component) {
|
||||
throw new ResolutionError(
|
||||
`conjugation on ${W.Component} returns other type (${conj.returns})`)
|
||||
}
|
||||
const mWZ = math.multiply.resolve([W.Component, Z.Component])
|
||||
const mZW = math.multiply.resolve([Z.Component, W.Component])
|
||||
const sub = math.subtract.resolve([mWZ.returns, mZW.returns])
|
||||
const add = math.add.resolve([mWZ.returns, mZW.returns])
|
||||
const cplx = math.complex.resolve([sub.returns, add.returns])
|
||||
return ReturnsAs(cplx, (w, z) => {
|
||||
const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im)))
|
||||
const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im))
|
||||
return cplx(real, imag)
|
||||
})
|
||||
})
|
||||
|
||||
export const negate = promoteUnary('negate')
|
||||
export const subtract = promoteBinary('subtract')
|
20
src/complex/helpers.js
Normal file
20
src/complex/helpers.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
export const promoteUnary = name => match(
|
||||
Complex,
|
||||
(math, C) => {
|
||||
const compOp = math.resolve(name, C.Component)
|
||||
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
|
||||
return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im)))
|
||||
})
|
||||
|
||||
export const promoteBinary = name => match(
|
||||
[Complex, Complex],
|
||||
(math, [W, Z]) => {
|
||||
const compOp = math.resolve(name, [W.Component, Z.Component])
|
||||
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
|
||||
return ReturnsAs(
|
||||
cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im)))
|
||||
})
|
24
src/complex/relational.js
Normal file
24
src/complex/relational.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {match, Any, Optional} from '#core/TypePatterns.js'
|
||||
|
||||
export const indistinguishable = match(
|
||||
[Complex, Complex, Optional([Any, Any])],
|
||||
(math, [W, Z, T]) => {
|
||||
let WComp = W.Component
|
||||
let ZComp = Z.Component
|
||||
if (T.length === 0) { // no tolerances
|
||||
const same = math.indistinguishable.resolve([WComp, ZComp])
|
||||
return Returns(
|
||||
BooleanT, (w, z) => same(w.re, z.re) && same(w.im, z.im))
|
||||
}
|
||||
const [[RT, AT]] = T
|
||||
const same = math.indistinguishable.resolve([WComp, ZComp, RT, AT])
|
||||
return Returns(
|
||||
BooleanT,
|
||||
(w, z, [[rT, aT]]) => {
|
||||
return same(w.re, z.re, rT, aT) && same(w.im, z.im, rT, aT)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {match} from "#core/helpers.js"
|
||||
import {Returns} from "#core/Type.js"
|
||||
import {Any} from "#core/TypePatterns.js"
|
||||
import {Any, match} from "#core/TypePatterns.js"
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
export const complex = [
|
||||
match(Any, (math, T) => {
|
||||
|
@ -25,3 +26,30 @@ export const complex = [
|
|||
return Returns(Complex(T), (r, m) => ({re: r, im: m}))
|
||||
})
|
||||
]
|
||||
|
||||
export const arg = match(
|
||||
Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re)))
|
||||
|
||||
/* Returns true if w is z multiplied by a complex unit */
|
||||
export const associate = match([Complex, Complex], (math, [W, Z]) => {
|
||||
if (Z.Component.complex) {
|
||||
throw new Error(
|
||||
`The group of units of type ${Z} is not yet implemented`)
|
||||
}
|
||||
const eq = math.equal.resolve([W, Z])
|
||||
const neg = math.negate.resolve(Z)
|
||||
const eqN = math.equal.resolve([W, neg.returns])
|
||||
const mult = math.multiply.resolve([Z, Z])
|
||||
const eqM = math.equal.resolve([W, mult.returns])
|
||||
const negM = math.negate.resolve(mult.returns)
|
||||
const eqNM = math.equal.resolve([W, negM.returns])
|
||||
const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component))
|
||||
return Returns(BooleanT, (w, z) => {
|
||||
if (eq(w, z) || eqN(w, neg(z))) return true
|
||||
const iz = mult(iZ, z)
|
||||
return eqM(w, iz) || eqNM(w, negM(iz))
|
||||
})
|
||||
})
|
||||
|
||||
export const cis = match(NumberT, Returns(Complex(NumberT), t => ({
|
||||
re: Math.cos(t), im: Math.sin(t)})))
|
||||
|
|
9
src/complex/utils.js
Normal file
9
src/complex/utils.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
export const isReal = match(Complex, (math, C) => {
|
||||
const eq = math.equal.resolve([C.Component, C.Component])
|
||||
const add = math.add.resolve([C.Component, C.Component])
|
||||
return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im)))
|
||||
})
|
13
src/core/Implementations.js
Normal file
13
src/core/Implementations.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export class Implementations {
|
||||
constructor(impOrImps) {
|
||||
if (Array.isArray(impOrImps)) {
|
||||
this.matchers = impOrImps
|
||||
} else this.matchers = [impOrImps]
|
||||
}
|
||||
}
|
||||
|
||||
export class ImplementationsGenerator {
|
||||
constructor(f) {
|
||||
this.generate = f
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
import ArrayKeyedMap from 'array-keyed-map'
|
||||
import {match} from './helpers.js'
|
||||
import {Passthru} from './TypePatterns.js'
|
||||
|
||||
// Generic types are callable, so we have no choice but to extend Function
|
||||
export class Type extends Function {
|
||||
|
@ -12,9 +10,8 @@ export class Type extends Function {
|
|||
// let the proxy out of this function, never `this`.
|
||||
const rewired = new Proxy(this, {
|
||||
apply: (target, thisForCall, args) => {
|
||||
const callThrough = thisForCall ?? target
|
||||
if (callThrough.specialize) return callThrough.specialize(...args)
|
||||
throw new TypeError(`Type ${callThrough} is not generic`)
|
||||
if (target.specialize) return target.specialize(...args)
|
||||
throw new TypeError(`Type ${target} is not generic`)
|
||||
},
|
||||
get: (target, prop, receiver) => {
|
||||
if (prop === 'isAproxy') return true
|
||||
|
@ -87,13 +84,3 @@ export const whichType = typs => Returns(TypeOfTypes, item => {
|
|||
}
|
||||
throw new TypeError(errorMsg)
|
||||
})
|
||||
|
||||
export const typeOf = match(Passthru, math => whichType(math.types))
|
||||
|
||||
// bootstrapping order matters, but order of exports in a module isn't
|
||||
// simply the order that the items are listed in the module. So we make
|
||||
// an explicitly ordered export of implementations for this sake:
|
||||
|
||||
export const bootstrapTypes = {
|
||||
Type, Undefined, TypeOfTypes, typeOf
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import ArrayKeyedMap from 'array-keyed-map'
|
||||
|
||||
import {ResolutionError, isPlainFunction, isPlainObject} from './helpers.js'
|
||||
import {Implementations, ImplementationsGenerator} from './Implementations.js'
|
||||
import {bootstrapTypes} from './type.js'
|
||||
import {Returns, whichType, Type} from './Type.js'
|
||||
import {
|
||||
Implementations, ImplementationsGenerator, Matcher, ResolutionError,
|
||||
isPlainFunction, isPlainObject, match, types
|
||||
} from './helpers.js'
|
||||
import {bootstrapTypes, Returns, whichType, Type} from './Type.js'
|
||||
import {matched, needsCollection, Passthru} from './TypePatterns.js'
|
||||
matched, needsCollection, Passthru, Matcher, match
|
||||
} from './TypePatterns.js'
|
||||
|
||||
const underResolution = Symbol('underResolution')
|
||||
|
||||
export class TypeDispatcher {
|
||||
constructor(...specs) {
|
||||
|
@ -14,7 +17,6 @@ export class TypeDispatcher {
|
|||
this._behaviors = {} // maps key to a map from type vectors to results
|
||||
this._fallbacks = {} // maps key to a catchall result
|
||||
// bootstrap the instance
|
||||
this.merge({types})
|
||||
this.merge(bootstrapTypes)
|
||||
for (const spec of specs) this.merge(spec)
|
||||
}
|
||||
|
@ -261,6 +263,7 @@ export class TypeDispatcher {
|
|||
if (!(key in this)) {
|
||||
throw new ReferenceError(`no method or value for key '${key}'`)
|
||||
}
|
||||
if (!Array.isArray(types)) types = [types]
|
||||
|
||||
const generatingDeps = this.resolve._genDepsOf?.length
|
||||
if (generatingDeps) this._addToDeps(key, types)
|
||||
|
@ -269,6 +272,10 @@ export class TypeDispatcher {
|
|||
// Return the cached resolution if it's there
|
||||
if (behave.has(types)) {
|
||||
const result = behave.get(types)
|
||||
if (result === underResolution) {
|
||||
throw new ResolutionError(
|
||||
`recursive resolution of ${key} on ${types}`)
|
||||
}
|
||||
if (generatingDeps
|
||||
&& typeof result === 'object'
|
||||
&& !(result instanceof Type)
|
||||
|
@ -328,13 +335,14 @@ export class TypeDispatcher {
|
|||
let theBehavior = () => undefined
|
||||
let finalBehavior
|
||||
this.resolve._genDepsOf.push([key, types])
|
||||
behave.set(types, underResolution)
|
||||
try { // Used to make sure not to return without popping _genDepsOf
|
||||
if (!('returns' in item)) {
|
||||
// looks like a factory
|
||||
try {
|
||||
theBehavior = item(
|
||||
DependencyRecorder(this, '', this, []),
|
||||
matched(template))
|
||||
matched(template, this))
|
||||
} catch (e) {
|
||||
e.message = `Error in factory for ${key} on ${types} `
|
||||
+ `(match data ${template}): ${e.message}`
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {Type, Undefined} from './Type.js'
|
||||
import {isPlainFunction} from './helpers.js'
|
||||
|
||||
export class TypePattern {
|
||||
match(typeSequence, options={}) {
|
||||
|
@ -64,12 +65,35 @@ class SequencePattern extends TypePattern {
|
|||
}
|
||||
}
|
||||
|
||||
class PredicatePattern extends TypePattern {
|
||||
constructor(predicate) {
|
||||
super()
|
||||
this.predicate = predicate
|
||||
}
|
||||
match(typeSequence, options={}) {
|
||||
const position = options.position ?? 0
|
||||
if (position >= typeSequence.length) return [-1, undefined]
|
||||
const actual = typeSequence[position]
|
||||
if (this.predicate(actual)) return [position + 1, actual]
|
||||
return [-1, Undefined]
|
||||
}
|
||||
sampleTypes() {
|
||||
throw new Error('sampleTypes() not yet implemented for PredicatePattern')
|
||||
}
|
||||
equal(other) {
|
||||
return super.equal(other) && this.predicate === other.predicate
|
||||
}
|
||||
}
|
||||
|
||||
export const pattern = patternOrSpec => {
|
||||
if (patternOrSpec instanceof TypePattern) return patternOrSpec
|
||||
if (patternOrSpec instanceof Type) {
|
||||
return new MatchTypePattern(patternOrSpec)
|
||||
}
|
||||
if (Array.isArray(patternOrSpec)) return new SequencePattern(patternOrSpec)
|
||||
if (isPlainFunction(patternOrSpec)) {
|
||||
return new PredicatePattern(patternOrSpec)
|
||||
}
|
||||
throw new TypeError(`Can't interpret '${patternOrSpec}' as a type pattern`)
|
||||
}
|
||||
|
||||
|
@ -150,9 +174,16 @@ class PassthruPattern extends TypePattern {
|
|||
export const Passthru = new PassthruPattern()
|
||||
|
||||
// returns the template just of matched types, dropping any actual types
|
||||
export const matched = (template) => {
|
||||
if (Array.isArray(template)) return template.map(matched)
|
||||
return template.matched ?? template
|
||||
export const matched = (template, math) => {
|
||||
if (Array.isArray(template)) {
|
||||
return template.map(pattern => matched(pattern, math))
|
||||
}
|
||||
if (template.matched) {
|
||||
let convert = template.convertor
|
||||
if (!convert.returns) convert = convert(math, template.actual)
|
||||
return convert.returns || template.matched
|
||||
}
|
||||
return template
|
||||
}
|
||||
|
||||
// checks if the template is just pass-through or needs collection
|
||||
|
@ -163,3 +194,12 @@ export const needsCollection = (template) => {
|
|||
}
|
||||
return 'actual' in template
|
||||
}
|
||||
|
||||
export class Matcher {
|
||||
constructor(spec, facOrBehave) {
|
||||
this.pattern = pattern(spec)
|
||||
this.does = facOrBehave
|
||||
}
|
||||
}
|
||||
|
||||
export const match = (spec, facOrBehave) => new Matcher(spec, facOrBehave)
|
||||
|
|
|
@ -4,8 +4,8 @@ import * as booleans from '#boolean/all.js'
|
|||
import * as generics from '#generic/all.js'
|
||||
import * as numbers from '#number/all.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
import {match, ResolutionError} from "#core/helpers.js"
|
||||
import {Any} from "#core/TypePatterns.js"
|
||||
import {ResolutionError} from "#core/helpers.js"
|
||||
import {match, Any} from "#core/TypePatterns.js"
|
||||
import {Returns, NotAType} from "#core/Type.js"
|
||||
import {plain} from "#number/helpers.js"
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import assert from 'assert'
|
||||
import {
|
||||
Matcher, match, isPlainObject, isPlainFunction, Implementations
|
||||
} from '../helpers.js'
|
||||
import {isPlainObject, isPlainFunction} from '../helpers.js'
|
||||
import {Implementations} from '../Implementations.js'
|
||||
import {Type, Undefined, TypeOfTypes} from '../Type.js'
|
||||
import {TypePattern} from '../TypePatterns.js'
|
||||
import {match, Matcher, TypePattern} from '../TypePatterns.js'
|
||||
|
||||
describe('Core helpers', () => {
|
||||
it('defines what Matchers are', () => {
|
||||
|
|
|
@ -1,41 +1,3 @@
|
|||
import {pattern, Passthru} from './TypePatterns.js'
|
||||
|
||||
export class Matcher {
|
||||
constructor(spec, facOrBehave) {
|
||||
this.pattern = pattern(spec)
|
||||
this.does = facOrBehave
|
||||
}
|
||||
}
|
||||
|
||||
export class Implementations {
|
||||
constructor(impOrImps) {
|
||||
if (Array.isArray(impOrImps)) {
|
||||
this.matchers = impOrImps
|
||||
} else this.matchers = [impOrImps]
|
||||
}
|
||||
}
|
||||
|
||||
export const match = (spec, facOrBehave) => new Matcher(spec, facOrBehave)
|
||||
|
||||
export class ImplementationsGenerator {
|
||||
constructor(f) {
|
||||
this.generate = f
|
||||
}
|
||||
}
|
||||
|
||||
// the archetypal example of needing an ImplementationsGenerator:
|
||||
// each TypeDispatcher must have a types property, which will be a
|
||||
// plain object of types. This must be a different object for each
|
||||
// TypeDispatcher, but the same object regardless of the types vector
|
||||
// passed to resolve. So an ordinary factory won't work, because it
|
||||
// would make a new plain object for each different types vector that
|
||||
// the property `types` was resolved with. And just a plain object
|
||||
// wouldn't work, because then every TypeDispatcher would have the same
|
||||
// collection of types (and modifying the types in one would affect them
|
||||
// all). Hence we do:
|
||||
|
||||
export const types = new ImplementationsGenerator(() => match(Passthru, {}))
|
||||
|
||||
export class ResolutionError extends TypeError {
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
|
|
26
src/core/type.js
Normal file
26
src/core/type.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import {ImplementationsGenerator} from './Implementations.js'
|
||||
import {Type, TypeOfTypes, Undefined, whichType} from './Type.js'
|
||||
import {match, Passthru} from './TypePatterns.js'
|
||||
|
||||
export const typeOf = match(Passthru, math => whichType(math.types))
|
||||
|
||||
// And now the types object itself: This property provides the archetypal
|
||||
// example of needing an ImplementationsGenerator. Each TypeDispatcher must
|
||||
// have a types property, which will be a plain object of types. This object
|
||||
// must be different for each TypeDispatcher, but within a TypeDispatcher,
|
||||
// it must be the same object regardless of the types vector passed to resolve.
|
||||
// So an ordinary factory won't work, because it would make a new plain object
|
||||
// for each different types vector that the property `types` was resolved with.
|
||||
// And just a plain object wouldn't work, because then every TypeDispatcher
|
||||
// would have the same collection of types (and modifying the types in one
|
||||
// would affect them all). Hence we do:
|
||||
|
||||
export const types = new ImplementationsGenerator(() => match(Passthru, {}))
|
||||
|
||||
// bootstrapping order matters, but order of exports in a module isn't
|
||||
// simply the order that the items are listed in the module. So we make
|
||||
// an explicitly ordered export of implementations for this sake:
|
||||
|
||||
export const bootstrapTypes = {
|
||||
types, Type, Undefined, TypeOfTypes, typeOf
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import {match} from '#core/helpers.js'
|
||||
import {TypeOfTypes, Undefined} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
export const indistinguishable = [
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {match} from '#core/helpers.js'
|
||||
import {NotAType, Returns, TypeOfTypes} from '#core/Type.js'
|
||||
import {Any} from "#core/TypePatterns.js"
|
||||
import {match,Any} from "#core/TypePatterns.js"
|
||||
import {boolnum} from "#number/helpers.js"
|
||||
|
||||
export const zero = [
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
describe('generic utility functions', () => {
|
||||
it('tests whether an element is zero', () => {
|
||||
|
@ -9,5 +10,16 @@ describe('generic utility functions', () => {
|
|||
assert(isZero(false))
|
||||
assert(!isZero(true))
|
||||
assert(isZero(undefined))
|
||||
assert(isZero(math.types.Complex(NumberT).zero))
|
||||
assert(isZero(math.complex(-2e-16, 4e-17)))
|
||||
assert(!isZero(math.complex(true)))
|
||||
})
|
||||
it('tests whether an element is real', () => {
|
||||
const {isReal} = math
|
||||
assert(isReal(Infinity))
|
||||
assert(isReal(false))
|
||||
assert(isReal(math.types.Complex(NumberT).one))
|
||||
assert(isReal(math.complex(-3.25, 4e-16)))
|
||||
assert(!isReal(math.complex(3, 4)))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {match} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {Any} from '#core/TypePatterns.js'
|
||||
import {match, Any} from '#core/TypePatterns.js'
|
||||
|
||||
export const conj = match(Any, (_math, T) => Returns(T, a => a))
|
||||
export const square = match(Any, (math, T) => {
|
||||
const mult = math.multiply.resolve([T, T])
|
||||
return Returns(mult.returns, a => mult(a, a))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {ImplementationsGenerator, match} from '#core/helpers.js'
|
||||
import {Passthru} from '#core/TypePatterns.js'
|
||||
import {ImplementationsGenerator} from '#core/Implementations.js'
|
||||
import {match, Passthru} from '#core/TypePatterns.js'
|
||||
|
||||
export const config = new ImplementationsGenerator(
|
||||
() => match(Passthru, {relTol: 1e-12, absTol: 1e-15}))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {ReturnsAs} from './helpers.js'
|
||||
import {match} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {Any, Passthru, matched} from '#core/TypePatterns.js'
|
||||
import {Any, Passthru, match, matched} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
export const equal = match([Any, Any], (math, [T, U]) => {
|
||||
|
@ -18,7 +17,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
|
|||
return boolnum(() => false)(math)
|
||||
}
|
||||
// Get the type of the first argument to the matching checker:
|
||||
const ByType = matched(exactChecker.template).flat()[0]
|
||||
const ByType = matched(exactChecker.template, math).flat()[0]
|
||||
// Now see if there are tolerances for that type:
|
||||
const typeConfig = math.resolve('config', [ByType])
|
||||
if ('relTol' in typeConfig) {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import {ReturnsAs} from './helpers.js'
|
||||
import {ResolutionError, match} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {Passthru} from "#core/TypePatterns.js"
|
||||
import {ResolutionError} from '#core/helpers.js'
|
||||
import {Passthru, match} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
// Most types are real. Have to make sure to redefine on all non-real types
|
||||
export const isReal = match(Passthru, boolnum(() => true))
|
||||
export const isZero = match(Passthru, (math, [T]) => {
|
||||
if (!T) { // called with no arguments
|
||||
throw new ResolutionError('isZero() requires one argument')
|
||||
|
|
|
@ -5,6 +5,16 @@ import * as numbers from './number/all.js'
|
|||
import * as complex from './complex/all.js'
|
||||
import {TypeDispatcher} from '#core/TypeDispatcher.js'
|
||||
|
||||
const math = new TypeDispatcher(booleans, coretypes, generics, numbers, complex)
|
||||
// At the moment, since we are not sorting patterns in any way,
|
||||
// order matters in the construction. Patterns that come later in
|
||||
// the following list will be tried earlier. (The rationale for that
|
||||
// ordering is that any time you merge something, it should supersede
|
||||
// whatever has been merged before.)
|
||||
// Hence, in building the math instance, we put generics first because
|
||||
// they should only kick in when there are not specific implementations,
|
||||
// and complex next becausewe want its conversion (which converts _any_
|
||||
// non-complex type to complex, potentially making a poor overload choice)
|
||||
// to be tried last.
|
||||
const math = new TypeDispatcher(generics, complex, booleans, coretypes, numbers)
|
||||
|
||||
export default math
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import {Type} from '#core/Type.js'
|
||||
import {match} from '#core/helpers.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
|
||||
export const NumberT = new Type(n => typeof n === 'number', {
|
||||
from: match(BooleanT, math => math.number.resolve([BooleanT])),
|
||||
typeName: 'NumberT', // since used before ever put in a TypeDispatcher
|
||||
one: 1,
|
||||
zero: 0,
|
||||
nan: NaN
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {NumberT} from './NumberT.js'
|
||||
|
||||
import {match} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
|
||||
export const plain = f => match(
|
||||
Array(f.length).fill(NumberT), Returns(NumberT, f))
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {match} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {Optional} from '#core/TypePatterns.js'
|
||||
import {match, Optional} from '#core/TypePatterns.js'
|
||||
import {boolnum} from './helpers.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {plain} from './helpers.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {match} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
const num = f => Returns(NumberT, f)
|
||||
|
|
|
@ -2,7 +2,7 @@ import {plain, boolnum} from './helpers.js'
|
|||
import {NumberT} from './NumberT.js'
|
||||
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {match} from '#core/helpers.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
|
||||
export const clone = plain(a => a)
|
||||
export const isnan = match(NumberT, boolnum(isNaN))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue