feat: methods on Complex (#24)
All checks were successful
/ test (push) Successful in 17s

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:
Glen Whitney 2025-04-25 14:17:34 +00:00 committed by Glen Whitney
parent 0ff00ff8cb
commit aad62df8ac
33 changed files with 428 additions and 106 deletions

View file

@ -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
})

View file

@ -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'

View file

@ -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)

View 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)))
})
})

View file

@ -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)
})
})

View 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)))
})
})

View file

@ -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
View 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
View 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
View 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)
})
})

View file

@ -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
View 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)))
})

View 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
}
}

View file

@ -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
}

View file

@ -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}`

View file

@ -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)

View file

@ -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"

View file

@ -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', () => {

View file

@ -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
View 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
}

View file

@ -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 = [

View file

@ -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 = [

View file

@ -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)))
})
})

View file

@ -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))

View file

@ -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}))

View file

@ -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) {

View file

@ -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')

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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'

View file

@ -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)

View file

@ -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))