feat: methods on Complex #24
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