From 8da23a84be3eb03de67d6975c204f3032bccb9eb Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Thu, 24 Apr 2025 13:09:15 -0700 Subject: [PATCH] feat: Add complex argument function `arg` Also removes circular imports from nanomath --- src/boolean/type.js | 2 +- src/complex/Complex.js | 2 +- src/complex/__test__/type.spec.js | 16 ++++++---- src/complex/arithmetic.js | 2 +- src/complex/type.js | 7 +++-- src/core/Implementations.js | 13 ++++++++ src/core/Type.js | 12 -------- src/core/TypeDispatcher.js | 12 ++++---- src/core/TypePatterns.js | 9 ++++++ src/core/__test__/TypeDispatcher.spec.js | 4 +-- src/core/__test__/helpers.spec.js | 7 ++--- src/core/helpers.js | 38 ------------------------ src/core/type.js | 26 ++++++++++++++++ src/coretypes/relational.js | 2 +- src/coretypes/utils.js | 3 +- src/generic/arithmetic.js | 3 +- src/generic/config.js | 4 +-- src/generic/relational.js | 3 +- src/generic/utils.js | 5 ++-- src/number/NumberT.js | 2 +- src/number/helpers.js | 2 +- src/number/relational.js | 3 +- src/number/type.js | 2 +- src/number/utils.js | 2 +- 24 files changed, 91 insertions(+), 90 deletions(-) create mode 100644 src/core/Implementations.js create mode 100644 src/core/type.js diff --git a/src/boolean/type.js b/src/boolean/type.js index 989bd20..63b2ae7 100644 --- a/src/boolean/type.js +++ b/src/boolean/type.js @@ -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' diff --git a/src/complex/Complex.js b/src/complex/Complex.js index 541d114..9dc0630 100644 --- a/src/complex/Complex.js +++ b/src/complex/Complex.js @@ -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 diff --git a/src/complex/__test__/type.spec.js b/src/complex/__test__/type.spec.js index a9d4725..a0c42cf 100644 --- a/src/complex/__test__/type.spec.js +++ b/src/complex/__test__/type.spec.js @@ -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) }) }) diff --git a/src/complex/arithmetic.js b/src/complex/arithmetic.js index 0cef720..75a91ea 100644 --- a/src/complex/arithmetic.js +++ b/src/complex/arithmetic.js @@ -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) => { diff --git a/src/complex/type.js b/src/complex/type.js index 9881bc5..6c0f410 100644 --- a/src/complex/type.js +++ b/src/complex/type.js @@ -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))) diff --git a/src/core/Implementations.js b/src/core/Implementations.js new file mode 100644 index 0000000..c53ea3e --- /dev/null +++ b/src/core/Implementations.js @@ -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 + } +} diff --git a/src/core/Type.js b/src/core/Type.js index 088b9e7..078f12d 100644 --- a/src/core/Type.js +++ b/src/core/Type.js @@ -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 -} diff --git a/src/core/TypeDispatcher.js b/src/core/TypeDispatcher.js index 121d182..9eaa6da 100644 --- a/src/core/TypeDispatcher.js +++ b/src/core/TypeDispatcher.js @@ -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) } diff --git a/src/core/TypePatterns.js b/src/core/TypePatterns.js index 7c3e8cd..a19cb2b 100644 --- a/src/core/TypePatterns.js +++ b/src/core/TypePatterns.js @@ -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) diff --git a/src/core/__test__/TypeDispatcher.spec.js b/src/core/__test__/TypeDispatcher.spec.js index 37e5618..c843e59 100644 --- a/src/core/__test__/TypeDispatcher.spec.js +++ b/src/core/__test__/TypeDispatcher.spec.js @@ -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" diff --git a/src/core/__test__/helpers.spec.js b/src/core/__test__/helpers.spec.js index 10849d6..46576eb 100644 --- a/src/core/__test__/helpers.spec.js +++ b/src/core/__test__/helpers.spec.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', () => { diff --git a/src/core/helpers.js b/src/core/helpers.js index dc05182..8d7b2b2 100644 --- a/src/core/helpers.js +++ b/src/core/helpers.js @@ -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) diff --git a/src/core/type.js b/src/core/type.js new file mode 100644 index 0000000..7452dd4 --- /dev/null +++ b/src/core/type.js @@ -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 +} diff --git a/src/coretypes/relational.js b/src/coretypes/relational.js index 4192dc1..a45cadf 100644 --- a/src/coretypes/relational.js +++ b/src/coretypes/relational.js @@ -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 = [ diff --git a/src/coretypes/utils.js b/src/coretypes/utils.js index 4386ac1..e89b74c 100644 --- a/src/coretypes/utils.js +++ b/src/coretypes/utils.js @@ -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 = [ diff --git a/src/generic/arithmetic.js b/src/generic/arithmetic.js index fe1556f..7c7c327 100644 --- a/src/generic/arithmetic.js +++ b/src/generic/arithmetic.js @@ -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]) diff --git a/src/generic/config.js b/src/generic/config.js index 9f421ec..72f8ced 100644 --- a/src/generic/config.js +++ b/src/generic/config.js @@ -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})) diff --git a/src/generic/relational.js b/src/generic/relational.js index 902835d..bd23f92 100644 --- a/src/generic/relational.js +++ b/src/generic/relational.js @@ -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]) => { diff --git a/src/generic/utils.js b/src/generic/utils.js index d4c8f87..b3766c4 100644 --- a/src/generic/utils.js +++ b/src/generic/utils.js @@ -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 diff --git a/src/number/NumberT.js b/src/number/NumberT.js index c3816a0..b915b85 100644 --- a/src/number/NumberT.js +++ b/src/number/NumberT.js @@ -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', { diff --git a/src/number/helpers.js b/src/number/helpers.js index 4ab75af..1d6bf9b 100644 --- a/src/number/helpers.js +++ b/src/number/helpers.js @@ -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)) diff --git a/src/number/relational.js b/src/number/relational.js index 8839c37..a4820a6 100644 --- a/src/number/relational.js +++ b/src/number/relational.js @@ -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' diff --git a/src/number/type.js b/src/number/type.js index ddb47cc..d8e6806 100644 --- a/src/number/type.js +++ b/src/number/type.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) diff --git a/src/number/utils.js b/src/number/utils.js index 08372c4..01fdbe4 100644 --- a/src/number/utils.js +++ b/src/number/utils.js @@ -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))