feat: Add complex argument function arg
All checks were successful
/ test (pull_request) Successful in 17s

Also removes circular imports from nanomath
This commit is contained in:
Glen Whitney 2025-04-24 13:09:15 -07:00
parent 474cc53d68
commit 8da23a84be
24 changed files with 91 additions and 90 deletions

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 {Returns, Type} from '#core/Type.js'
import {match} from '#core/helpers.js'
import {match} from '#core/TypePatterns.js'
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z

View file

@ -1,12 +1,18 @@
import assert from 'assert'
import math from '#nanomath'
const cplx = math.complex
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)
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)
})
})

View file

@ -1,5 +1,5 @@
import {Complex} from './Complex.js'
import {match} from '#core/helpers.js'
import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js'
export const absquare = match(Complex, (math, C) => {

View file

@ -1,7 +1,7 @@
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 {NumberT} from '#number/NumberT.js'
export const complex = [
match(Any, (math, T) => {
@ -25,3 +25,6 @@ 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)))

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 {
@ -87,13 +85,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,12 @@
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'
export class TypeDispatcher {
constructor(...specs) {
@ -14,7 +15,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)
}

View file

@ -193,3 +193,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,6 +1,5 @@
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 square = match(Any, (math, T) => {
const mult = math.multiply.resolve([T, T])

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]) => {

View file

@ -1,7 +1,6 @@
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"
export const isZero = match(Passthru, (math, [T]) => {
if (!T) { // called with no arguments

View file

@ -1,5 +1,5 @@
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', {

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