diff --git a/src/complex/Complex.js b/src/complex/Complex.js index d934fd1..541d114 100644 --- a/src/complex/Complex.js +++ b/src/complex/Complex.js @@ -1,4 +1,4 @@ -import {Type} from '#core/Type.js' +import {Returns, Type} from '#core/Type.js' import {match} from '#core/helpers.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) diff --git a/src/complex/__test__/arithmetic.spec.js b/src/complex/__test__/arithmetic.spec.js new file mode 100644 index 0000000..02dd56b --- /dev/null +++ b/src/complex/__test__/arithmetic.spec.js @@ -0,0 +1,26 @@ +import assert from 'assert' +import math from '#nanomath' + +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) + assert.deepStrictEqual(math.add(z, cplx(-1, 1)), cplx(2, 5)) + assert.deepStrictEqual(math.add(z, cplx(true, false)), cplx(4, 4)) + assert.deepStrictEqual(math.add(cplx(false, true), z), cplx(3, 5)) + assert.deepStrictEqual( + math.add(cplx(z, z), cplx(cplx(0.5, 0.5), z)), + cplx(cplx(3.5, 4.5), cplx(6, 8))) + assert.deepStrictEqual(math.add(z, 5), cplx(8, 4)) + assert.deepStrictEqual(math.add(true, z), cplx(4, 4)) + assert.deepStrictEqual(math.add(cplx(z,z), 10), cplx(cplx(13, 4), z)) + assert.deepStrictEqual( + math.add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4))) + }) +}) diff --git a/src/complex/__test__/complex.spec.js b/src/complex/__test__/type.spec.js similarity index 100% rename from src/complex/__test__/complex.spec.js rename to src/complex/__test__/type.spec.js diff --git a/src/complex/all.js b/src/complex/all.js index 9795516..5c261d4 100644 --- a/src/complex/all.js +++ b/src/complex/all.js @@ -1,2 +1,3 @@ export * as typeDefinition from './Complex.js' +export * as arithmetic from './arithmetic.js' export * as type from './type.js' diff --git a/src/complex/arithmetic.js b/src/complex/arithmetic.js new file mode 100644 index 0000000..0cef720 --- /dev/null +++ b/src/complex/arithmetic.js @@ -0,0 +1,17 @@ +import {Complex} from './Complex.js' +import {match} from '#core/helpers.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 = match([Complex, Complex], (math, [C, D]) => { + const addComps = math.add.resolve([C.Component, D.Component]) + const cplx = math.complex.resolve([addComps.returns, addComps.returns]) + return ReturnsAs( + cplx, (w,z) => cplx(addComps(w.re, z.re), addComps(w.im, z.im))) +}) diff --git a/src/core/TypeDispatcher.js b/src/core/TypeDispatcher.js index 202afe1..121d182 100644 --- a/src/core/TypeDispatcher.js +++ b/src/core/TypeDispatcher.js @@ -334,7 +334,7 @@ export class TypeDispatcher { 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}` diff --git a/src/core/TypePatterns.js b/src/core/TypePatterns.js index eb73afd..7c3e8cd 100644 --- a/src/core/TypePatterns.js +++ b/src/core/TypePatterns.js @@ -1,4 +1,5 @@ import {Type, Undefined} from './Type.js' +import {isPlainFunction} from './helpers.js' export class TypePattern { match(typeSequence, options={}) { @@ -64,12 +65,34 @@ class SequencePattern extends TypePattern { } } +class PredicatePattern extends TypePattern { + constructor(predicate) { + super() + this.predicate = predicate + } + match(typeSequence, options={}) { + const position = options.position ?? 0 + 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 +173,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 diff --git a/src/generic/relational.js b/src/generic/relational.js index 810c06a..902835d 100644 --- a/src/generic/relational.js +++ b/src/generic/relational.js @@ -18,7 +18,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) { diff --git a/src/nanomath.js b/src/nanomath.js index c133cbf..3038472 100644 --- a/src/nanomath.js +++ b/src/nanomath.js @@ -5,6 +5,14 @@ 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 complex first because +// we 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(complex, generics, booleans, coretypes, numbers) export default math