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 {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 {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
import {NumberT} from '#number/NumberT.js' import {NumberT} from '#number/NumberT.js'

View file

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

View file

@ -1,12 +1,18 @@
import assert from 'assert' import assert from 'assert'
import math from '#nanomath' import math from '#nanomath'
const cplx = math.complex
describe('complex type operations', () => { describe('complex type operations', () => {
it('converts to number', () => { it('converts to number', () => {
assert.deepStrictEqual(math.complex(3), {re: 3, im: 0}) assert.deepStrictEqual(cplx(3), {re: 3, im: 0})
assert.deepStrictEqual(math.complex(NaN), {re: NaN, im: NaN}) assert.deepStrictEqual(cplx(NaN), {re: NaN, im: NaN})
assert.deepStrictEqual(math.complex(3, -1), {re: 3, im: -1}) assert.deepStrictEqual(cplx(3, -1), {re: 3, im: -1})
assert.deepStrictEqual(math.complex(false, true), {re: false, im: true}) assert.deepStrictEqual(cplx(false, true), {re: false, im: true})
assert.throws(() => math.complex(3, false), RangeError) 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 {Complex} from './Complex.js'
import {match} from '#core/helpers.js' import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js' import {ReturnsAs} from '#generic/helpers.js'
export const absquare = match(Complex, (math, C) => { export const absquare = match(Complex, (math, C) => {

View file

@ -1,7 +1,7 @@
import {Complex} from './Complex.js' import {Complex} from './Complex.js'
import {match} from "#core/helpers.js"
import {Returns} from "#core/Type.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 = [ export const complex = [
match(Any, (math, T) => { match(Any, (math, T) => {
@ -25,3 +25,6 @@ export const complex = [
return Returns(Complex(T), (r, m) => ({re: r, im: m})) 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 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 // Generic types are callable, so we have no choice but to extend Function
export class Type extends Function { export class Type extends Function {
@ -87,13 +85,3 @@ export const whichType = typs => Returns(TypeOfTypes, item => {
} }
throw new TypeError(errorMsg) 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 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 { import {
Implementations, ImplementationsGenerator, Matcher, ResolutionError, matched, needsCollection, Passthru, Matcher, match
isPlainFunction, isPlainObject, match, types } from './TypePatterns.js'
} from './helpers.js'
import {bootstrapTypes, Returns, whichType, Type} from './Type.js'
import {matched, needsCollection, Passthru} from './TypePatterns.js'
export class TypeDispatcher { export class TypeDispatcher {
constructor(...specs) { constructor(...specs) {
@ -14,7 +15,6 @@ export class TypeDispatcher {
this._behaviors = {} // maps key to a map from type vectors to results this._behaviors = {} // maps key to a map from type vectors to results
this._fallbacks = {} // maps key to a catchall result this._fallbacks = {} // maps key to a catchall result
// bootstrap the instance // bootstrap the instance
this.merge({types})
this.merge(bootstrapTypes) this.merge(bootstrapTypes)
for (const spec of specs) this.merge(spec) for (const spec of specs) this.merge(spec)
} }

View file

@ -193,3 +193,12 @@ export const needsCollection = (template) => {
} }
return 'actual' in 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 generics from '#generic/all.js'
import * as numbers from '#number/all.js' import * as numbers from '#number/all.js'
import {NumberT} from '#number/NumberT.js' import {NumberT} from '#number/NumberT.js'
import {match, ResolutionError} from "#core/helpers.js" import {ResolutionError} from "#core/helpers.js"
import {Any} from "#core/TypePatterns.js" import {match, Any} from "#core/TypePatterns.js"
import {Returns, NotAType} from "#core/Type.js" import {Returns, NotAType} from "#core/Type.js"
import {plain} from "#number/helpers.js" import {plain} from "#number/helpers.js"

View file

@ -1,9 +1,8 @@
import assert from 'assert' import assert from 'assert'
import { import {isPlainObject, isPlainFunction} from '../helpers.js'
Matcher, match, isPlainObject, isPlainFunction, Implementations import {Implementations} from '../Implementations.js'
} from '../helpers.js'
import {Type, Undefined, TypeOfTypes} from '../Type.js' import {Type, Undefined, TypeOfTypes} from '../Type.js'
import {TypePattern} from '../TypePatterns.js' import {match, Matcher, TypePattern} from '../TypePatterns.js'
describe('Core helpers', () => { describe('Core helpers', () => {
it('defines what Matchers are', () => { 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 { export class ResolutionError extends TypeError {
constructor(...args) { constructor(...args) {
super(...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 {TypeOfTypes, Undefined} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {boolnum} from '#number/helpers.js' import {boolnum} from '#number/helpers.js'
export const indistinguishable = [ export const indistinguishable = [

View file

@ -1,6 +1,5 @@
import {match} from '#core/helpers.js'
import {NotAType, Returns, TypeOfTypes} from '#core/Type.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" import {boolnum} from "#number/helpers.js"
export const zero = [ export const zero = [

View file

@ -1,6 +1,5 @@
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.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) => { export const square = match(Any, (math, T) => {
const mult = math.multiply.resolve([T, T]) const mult = math.multiply.resolve([T, T])

View file

@ -1,5 +1,5 @@
import {ImplementationsGenerator, match} from '#core/helpers.js' import {ImplementationsGenerator} from '#core/Implementations.js'
import {Passthru} from '#core/TypePatterns.js' import {match, Passthru} from '#core/TypePatterns.js'
export const config = new ImplementationsGenerator( export const config = new ImplementationsGenerator(
() => match(Passthru, {relTol: 1e-12, absTol: 1e-15})) () => match(Passthru, {relTol: 1e-12, absTol: 1e-15}))

View file

@ -1,7 +1,6 @@
import {ReturnsAs} from './helpers.js' import {ReturnsAs} from './helpers.js'
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.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' import {boolnum} from '#number/helpers.js'
export const equal = match([Any, Any], (math, [T, U]) => { export const equal = match([Any, Any], (math, [T, U]) => {

View file

@ -1,7 +1,6 @@
import {ReturnsAs} from './helpers.js' import {ReturnsAs} from './helpers.js'
import {ResolutionError, match} from '#core/helpers.js' import {ResolutionError} from '#core/helpers.js'
import {Returns} from '#core/Type.js' import {Passthru, match} from "#core/TypePatterns.js"
import {Passthru} from "#core/TypePatterns.js"
export const isZero = match(Passthru, (math, [T]) => { export const isZero = match(Passthru, (math, [T]) => {
if (!T) { // called with no arguments if (!T) { // called with no arguments

View file

@ -1,5 +1,5 @@
import {Type} from '#core/Type.js' import {Type} from '#core/Type.js'
import {match} from '#core/helpers.js' import {match} from '#core/TypePatterns.js'
import {BooleanT} from '#boolean/BooleanT.js' import {BooleanT} from '#boolean/BooleanT.js'
export const NumberT = new Type(n => typeof n === 'number', { export const NumberT = new Type(n => typeof n === 'number', {

View file

@ -1,7 +1,7 @@
import {NumberT} from './NumberT.js' import {NumberT} from './NumberT.js'
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.js' import {Returns} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
export const plain = f => match( export const plain = f => match(
Array(f.length).fill(NumberT), Returns(NumberT, f)) 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 {Returns} from '#core/Type.js'
import {Optional} from '#core/TypePatterns.js' import {match, Optional} from '#core/TypePatterns.js'
import {boolnum} from './helpers.js' import {boolnum} from './helpers.js'
import {NumberT} from './NumberT.js' import {NumberT} from './NumberT.js'

View file

@ -1,7 +1,7 @@
import {plain} from './helpers.js' import {plain} from './helpers.js'
import {BooleanT} from '#boolean/BooleanT.js' import {BooleanT} from '#boolean/BooleanT.js'
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.js' import {Returns} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {NumberT} from '#number/NumberT.js' import {NumberT} from '#number/NumberT.js'
const num = f => Returns(NumberT, f) const num = f => Returns(NumberT, f)

View file

@ -2,7 +2,7 @@ import {plain, boolnum} from './helpers.js'
import {NumberT} from './NumberT.js' import {NumberT} from './NumberT.js'
import {Returns} from '#core/Type.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 clone = plain(a => a)
export const isnan = match(NumberT, boolnum(isNaN)) export const isnan = match(NumberT, boolnum(isNaN))