From cc4d77f128ae29540716999177966264d4e3d80a Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 28 Apr 2025 14:29:08 -0700 Subject: [PATCH] feat: add Vector generic type --- src/nanomath.js | 10 ++++--- src/vector/Vector.js | 43 ++++++++++++++++++++++++++++++ src/vector/__test__/Vector.spec.js | 35 ++++++++++++++++++++++++ src/vector/all.js | 1 + 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/vector/Vector.js create mode 100644 src/vector/__test__/Vector.spec.js create mode 100644 src/vector/all.js diff --git a/src/nanomath.js b/src/nanomath.js index ddeeeba..b95aff5 100644 --- a/src/nanomath.js +++ b/src/nanomath.js @@ -1,8 +1,9 @@ -import * as booleans from './boolean/all.js' import * as coretypes from './coretypes/all.js' +import * as booleans from './boolean/all.js' +import * as complex from './complex/all.js' import * as generics from './generic/all.js' import * as numbers from './number/all.js' -import * as complex from './complex/all.js' +import * as vectors from './vector/all.js' import {TypeDispatcher} from '#core/TypeDispatcher.js' // At the moment, since we are not sorting patterns in any way, @@ -12,9 +13,10 @@ import {TypeDispatcher} from '#core/TypeDispatcher.js' // 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_ +// and complex next 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(generics, complex, booleans, coretypes, numbers) +const math = new TypeDispatcher( + generics, complex, vectors, booleans, coretypes, numbers) export default math diff --git a/src/vector/Vector.js b/src/vector/Vector.js new file mode 100644 index 0000000..0f82e4f --- /dev/null +++ b/src/vector/Vector.js @@ -0,0 +1,43 @@ +import {NotAType, Type} from '#core/Type.js' + +const isVector = v => Array.isArray(v) + +export const Vector = new Type(isVector, { + specialize(CompType) { + const compTest = CompType.test + const specTest = v => isVector(v) && v.every(compTest) + const typeName = `Vector(${CompType})` + if (CompType.concrete) { + const typeOptions = {typeName} + if ('zero' in CompType) typeOptions.zero = [CompType.zero] + const vectorCompType = new Type(specTest, typeOptions) + vectorCompType.Component = CompType + vectorCompType.vector = true + return vectorCompType + } + // Wrapping a generic type in Vector + return new Type(specTest, { + typeName, + specialize: (...args) => this.specialize(CompType.specialize(...args)), + specializesTo: VT => this.specializesTo(VT) + && CompType.specializesTo(VT.Component), + refine: (v, typer) => { + const eltTypes = v.map(elt => CompType.refine(elt, typer)) + const newCompType = eltTypes[0] + if (eltTypes.some(T => T !== newCompType)) { + throw new TypeError( + `can't refine ${typeName} to ${v}; ` + + `not all entries have type ${newCompType}`) + } + return this.specialize(newCompType) + } + }) + }, + specializesTo: VT => VT.vector, + refine(v, typer) { + const eltTypes = v.map(elt => typer(elt)) + let compType = eltTypes[0] + if (eltTypes.some(T => T !== compType)) compType = NotAType + return this.specialize(compType) + } +}) diff --git a/src/vector/__test__/Vector.spec.js b/src/vector/__test__/Vector.spec.js new file mode 100644 index 0000000..5233bb5 --- /dev/null +++ b/src/vector/__test__/Vector.spec.js @@ -0,0 +1,35 @@ +import assert from 'assert' +import {Vector} from '../Vector.js' +import {NotAType} from '#core/Type.js' +import math from '#nanomath' + +describe('Vector Type', () => { + it('correctly recognizes vectors', () => { + assert(Vector.test([3, 4])) + assert(!Vector.test({re: 3, im: 4})) + }) + it('can fully type vectors', () => { + const {BooleanT, Complex, NumberT} = math.types + const typ = math.typeOf + const cplx = math.complex + assert.strictEqual(typ([3, 4]), Vector(NumberT)) + assert.strictEqual(typ([true, false]), Vector(BooleanT)) + assert.strictEqual(typ([3, false]), Vector(NotAType)) + assert.strictEqual(typ([cplx(3, 4), cplx(5)]), Vector(Complex(NumberT))) + assert.strictEqual(typ([[3, 4], [5]]), Vector(Vector(NumberT))) + }) + it('can refine when nested', () => { + const {Complex, NumberT} = math.types + const cplx = math.complex + const VecComplex = Vector(Complex) + const vcEx = [cplx(3, 4), cplx(5)] + assert(VecComplex.test(vcEx)) + const vcType = VecComplex.refine(vcEx, math.typeOf) + assert.strictEqual(vcType, Vector(Complex(NumberT))) + const CplxVec = Complex(Vector) + const cvEx = cplx([3,4], [5, 0]) + assert(CplxVec.test(cvEx)) + const cvType = CplxVec.refine(cvEx, math.typeOf) + assert.strictEqual(cvType, Complex(Vector(NumberT))) + }) +}) diff --git a/src/vector/all.js b/src/vector/all.js new file mode 100644 index 0000000..b5fe798 --- /dev/null +++ b/src/vector/all.js @@ -0,0 +1 @@ +export * as typeDefinition from './Vector.js'