diff --git a/src/boolean/BooleanT.js b/src/boolean/BooleanT.js index f2792ed..6f4ede6 100644 --- a/src/boolean/BooleanT.js +++ b/src/boolean/BooleanT.js @@ -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 }) 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 d934fd1..9dc0630 100644 --- a/src/complex/Complex.js +++ b/src/complex/Complex.js @@ -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) diff --git a/src/complex/__test__/arithmetic.spec.js b/src/complex/__test__/arithmetic.spec.js new file mode 100644 index 0000000..966843b --- /dev/null +++ b/src/complex/__test__/arithmetic.spec.js @@ -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))) + }) +}) diff --git a/src/complex/__test__/complex.spec.js b/src/complex/__test__/complex.spec.js deleted file mode 100644 index a9d4725..0000000 --- a/src/complex/__test__/complex.spec.js +++ /dev/null @@ -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) - }) -}) diff --git a/src/complex/__test__/type.spec.js b/src/complex/__test__/type.spec.js new file mode 100644 index 0000000..6ea0875 --- /dev/null +++ b/src/complex/__test__/type.spec.js @@ -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))) + }) +}) diff --git a/src/complex/all.js b/src/complex/all.js index 9795516..023c144 100644 --- a/src/complex/all.js +++ b/src/complex/all.js @@ -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' diff --git a/src/complex/arithmetic.js b/src/complex/arithmetic.js new file mode 100644 index 0000000..d50b993 --- /dev/null +++ b/src/complex/arithmetic.js @@ -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') diff --git a/src/complex/helpers.js b/src/complex/helpers.js new file mode 100644 index 0000000..84e828f --- /dev/null +++ b/src/complex/helpers.js @@ -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))) + }) diff --git a/src/complex/relational.js b/src/complex/relational.js new file mode 100644 index 0000000..a8c58f3 --- /dev/null +++ b/src/complex/relational.js @@ -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) + }) + }) + diff --git a/src/complex/type.js b/src/complex/type.js index 9881bc5..e83aabb 100644 --- a/src/complex/type.js +++ b/src/complex/type.js @@ -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)}))) diff --git a/src/complex/utils.js b/src/complex/utils.js new file mode 100644 index 0000000..2fef03a --- /dev/null +++ b/src/complex/utils.js @@ -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))) +}) 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..555b547 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 { @@ -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 -} diff --git a/src/core/TypeDispatcher.js b/src/core/TypeDispatcher.js index 202afe1..2b997e6 100644 --- a/src/core/TypeDispatcher.js +++ b/src/core/TypeDispatcher.js @@ -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}` diff --git a/src/core/TypePatterns.js b/src/core/TypePatterns.js index eb73afd..14f41c0 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,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) 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/__test__/utils.spec.js b/src/generic/__test__/utils.spec.js index e178a2d..f8b6d53 100644 --- a/src/generic/__test__/utils.spec.js +++ b/src/generic/__test__/utils.spec.js @@ -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))) }) }) diff --git a/src/generic/arithmetic.js b/src/generic/arithmetic.js index fe1556f..cf67fdc 100644 --- a/src/generic/arithmetic.js +++ b/src/generic/arithmetic.js @@ -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)) 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 810c06a..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]) => { @@ -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) { diff --git a/src/generic/utils.js b/src/generic/utils.js index d4c8f87..d696d6c 100644 --- a/src/generic/utils.js +++ b/src/generic/utils.js @@ -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') diff --git a/src/nanomath.js b/src/nanomath.js index c133cbf..ddeeeba 100644 --- a/src/nanomath.js +++ b/src/nanomath.js @@ -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 diff --git a/src/number/NumberT.js b/src/number/NumberT.js index c3816a0..415a37d 100644 --- a/src/number/NumberT.js +++ b/src/number/NumberT.js @@ -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 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))