feat: Start adding vector arithmetic

So far, abs, add, norm, normsq, and sum are supported. To get them
  to work, also implements the following:
  * refactor: Use ReturnType function rather than just accessing .returns
  * feat: distinguish marking a function as a behavior from its return type
  * refactor: Rename `NotAType` to `Unknown` because it must be made closer
    to a bona fide type for the sake of inhomogeneous vectors
  * feat: make resolving a TypeDispatcher method on a type vector including
    `Unknown` into a no-op; that simplifies a number of generic behaviors
  * feat: add `haszero` method parallel to `hasnan`
  * feat: track the Vector nesting depth of Vector specializations
This commit is contained in:
Glen Whitney 2025-05-03 19:59:44 -07:00
parent ec97b0e20a
commit cb3a93dd1c
26 changed files with 238 additions and 171 deletions

View file

@ -1,7 +1,7 @@
import assert from 'assert' import assert from 'assert'
import math from '#nanomath' import math from '#nanomath'
import {Complex} from '../Complex.js' import {Complex} from '../Complex.js'
import {OneOf, ReturnTyping} from '#core/Type.js' import {OneOf, ReturnType, ReturnTyping} from '#core/Type.js'
import {NumberT} from '#number/NumberT.js' import {NumberT} from '#number/NumberT.js'
@ -38,13 +38,13 @@ describe('complex type operations', () => {
}) })
it('computes cis of an angle', () => { it('computes cis of an angle', () => {
const cis = math.cis.resolve(NumberT) const cis = math.cis.resolve(NumberT)
assert.strictEqual(cis.returns, OneOf(Complex(NumberT), NumberT)) assert.strictEqual(ReturnType(cis), OneOf(Complex(NumberT), NumberT))
assert.strictEqual(cis(0), 1) assert.strictEqual(cis(0), 1)
assert.strictEqual(cis(Math.PI), -1) assert.strictEqual(cis(Math.PI), -1)
assert(math.equal(cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2))) assert(math.equal(cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2)))
math.config.returnTyping = ReturnTyping.full math.config.returnTyping = ReturnTyping.full
const ccis = math.cis.resolve(NumberT) const ccis = math.cis.resolve(NumberT)
assert.strictEqual(ccis.returns, Complex(NumberT)) assert.strictEqual(ReturnType(ccis), Complex(NumberT))
const one = ccis(0) const one = ccis(0)
assert(one !== 1) assert(one !== 1)
assert(math.equal(one, 1)) assert(math.equal(one, 1))

View file

@ -1,7 +1,7 @@
import {Complex} from './Complex.js' import {Complex} from './Complex.js'
import {maybeComplex, promoteBinary, promoteUnary} from './helpers.js' import {maybeComplex, promoteBinary, promoteUnary} from './helpers.js'
import {ResolutionError} from '#core/helpers.js' import {ResolutionError} from '#core/helpers.js'
import {Returns, ReturnTyping} from '#core/Type.js' import {Returns, ReturnType, ReturnTyping} from '#core/Type.js'
import {match} from '#core/TypePatterns.js' import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js' import {ReturnsAs} from '#generic/helpers.js'
@ -9,7 +9,7 @@ const {conservative, full, free} = ReturnTyping
export const normsq = match(Complex, (math, C, strategy) => { export const normsq = match(Complex, (math, C, strategy) => {
const compNormsq = math.normsq.resolve(C.Component, full) const compNormsq = math.normsq.resolve(C.Component, full)
const R = compNormsq.returns const R = ReturnType(compNormsq)
const add = math.add.resolve([R,R], strategy) const add = math.add.resolve([R,R], strategy)
return ReturnsAs(add, z => add(compNormsq(z.re), compNormsq(z.im))) return ReturnsAs(add, z => add(compNormsq(z.re), compNormsq(z.im)))
}) })
@ -19,19 +19,21 @@ export const add = promoteBinary('add')
export const conj = match(Complex, (math, C, strategy) => { export const conj = match(Complex, (math, C, strategy) => {
const neg = math.negate.resolve(C.Component, full) const neg = math.negate.resolve(C.Component, full)
const compConj = math.conj.resolve(C.Component, full) const compConj = math.conj.resolve(C.Component, full)
const cplx = maybeComplex(math, strategy, compConj.returns, neg.returns) const cplx = maybeComplex(
math, strategy, ReturnType(compConj), ReturnType(neg))
return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im))) return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im)))
}) })
export const divide = [ export const divide = [
match([Complex, T => !T.complex], (math, [C, R], strategy) => { match([Complex, T => !T.complex], (math, [C, R], strategy) => {
const div = math.divide.resolve([C.Component, R], full) const div = math.divide.resolve([C.Component, R], full)
const cplx = maybeComplex(math, strategy, div.returns, div.returns) const NewComp = ReturnType(div)
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r))) return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r)))
}), }),
match([Complex, Complex], (math, [W, Z], strategy) => { match([Complex, Complex], (math, [W, Z], strategy) => {
const inv = math.invert.resolve(Z, full) const inv = math.invert.resolve(Z, full)
const mult = math.multiply.resolve([W, inv.returns], strategy) const mult = math.multiply.resolve([W, ReturnType(inv)], strategy)
return ReturnsAs(mult, (w, z) => mult(w, inv(z))) return ReturnsAs(mult, (w, z) => mult(w, inv(z)))
}) })
] ]
@ -39,8 +41,9 @@ export const divide = [
export const invert = match(Complex, (math, C, strategy) => { export const invert = match(Complex, (math, C, strategy) => {
const conj = math.conj.resolve(C, full) const conj = math.conj.resolve(C, full)
const normsq = math.normsq.resolve(C, full) const normsq = math.normsq.resolve(C, full)
const div = math.divide.resolve([C.Component, normsq.returns], full) const div = math.divide.resolve([C.Component, ReturnType(normsq)], full)
const cplx = maybeComplex(math, strategy, div.returns, div.returns) const NewComp = ReturnType(div)
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
return ReturnsAs(cplx, z => { return ReturnsAs(cplx, z => {
const c = conj(z) const c = conj(z)
const d = normsq(z) const d = normsq(z)
@ -53,7 +56,8 @@ export const invert = match(Complex, (math, C, strategy) => {
export const multiply = [ export const multiply = [
match([T => !T.complex, Complex], (math, [R, C], strategy) => { match([T => !T.complex, Complex], (math, [R, C], strategy) => {
const mult = math.multiply.resolve([R, C.Component], full) const mult = math.multiply.resolve([R, C.Component], full)
const cplx = maybeComplex(math, strategy, mult.returns, mult.returns) const NewComp = ReturnType(mult)
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
return ReturnsAs(cplx, (r, z) => cplx(mult(r, z.re), mult(r, z.im))) return ReturnsAs(cplx, (r, z) => cplx(mult(r, z.re), mult(r, z.im)))
}), }),
match([Complex, T => !T.complex], (math, [C, R], strategy) => { match([Complex, T => !T.complex], (math, [C, R], strategy) => {
@ -62,15 +66,19 @@ export const multiply = [
}), }),
match([Complex, Complex], (math, [W, Z], strategy) => { match([Complex, Complex], (math, [W, Z], strategy) => {
const conj = math.conj.resolve(W.Component, full) const conj = math.conj.resolve(W.Component, full)
if (conj.returns !== W.Component) { if (ReturnType(conj) !== W.Component) {
throw new ResolutionError( throw new ResolutionError(
`conjugation on ${W.Component} returns type (${conj.returns})`) `conjugation on ${W.Component} returns different type `
+ `(${ReturnType(conj)})`)
} }
const mWZ = math.multiply.resolve([W.Component, Z.Component], full) const mWZ = math.multiply.resolve([W.Component, Z.Component], full)
const mZW = math.multiply.resolve([Z.Component, W.Component], full) const mZW = math.multiply.resolve([Z.Component, W.Component], full)
const sub = math.subtract.resolve([mWZ.returns, mZW.returns], full) const TWZ = ReturnType(mWZ)
const add = math.add.resolve([mWZ.returns, mZW.returns], full) const TZW = ReturnType(mZW)
const cplx = maybeComplex(math, strategy, sub.returns, add.returns) const sub = math.subtract.resolve([TWZ, TZW], full)
const add = math.add.resolve([TWZ, TZW], full)
const cplx = maybeComplex(
math, strategy, ReturnType(sub), ReturnType(add))
return ReturnsAs(cplx, (w, z) => { return ReturnsAs(cplx, (w, z) => {
const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im))) 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)) const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im))
@ -85,7 +93,7 @@ export const negate = promoteUnary('negate')
// integer coordinates. // integer coordinates.
export const sqrt = match(Complex, (math, C, strategy) => { export const sqrt = match(Complex, (math, C, strategy) => {
const re = math.re.resolve(C) const re = math.re.resolve(C)
const R = re.returns const R = ReturnType(re)
const isReal = math.isReal.resolve(C) const isReal = math.isReal.resolve(C)
// dependencies for the real case: // dependencies for the real case:
const zComp = math.zero(C.Component) const zComp = math.zero(C.Component)
@ -98,8 +106,8 @@ export const sqrt = match(Complex, (math, C, strategy) => {
const cplx = math.complex.resolve([C.Component, C.Component], full) const cplx = math.complex.resolve([C.Component, C.Component], full)
// additional dependencies for the complex case // additional dependencies for the complex case
const abs = math.abs.resolve(C, full) const abs = math.abs.resolve(C, full)
if (abs.returns !== R) { if (ReturnType(abs) !== R) {
throw new TypeError(`abs on ${C} returns ${abs.returns}, not ${R}`) throw new TypeError(`abs on ${C} returns ${ReturnType(abs)}, not ${R}`)
} }
const addRR = math.add.resolve([R, R], conservative) const addRR = math.add.resolve([R, R], conservative)
const twoR = addRR(oneR, oneR) const twoR = addRR(oneR, oneR)

View file

@ -1,5 +1,5 @@
import {Complex} from './Complex.js' import {Complex} from './Complex.js'
import {ReturnTyping} from '#core/Type.js' import {ReturnType, ReturnTyping} from '#core/Type.js'
import {match} from '#core/TypePatterns.js' import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js' import {ReturnsAs} from '#generic/helpers.js'
@ -8,7 +8,7 @@ const {free, full} = ReturnTyping
export const maybeComplex = (math, strategy, Real, Imag) => { export const maybeComplex = (math, strategy, Real, Imag) => {
if (strategy !== free) return math.complex.resolve([Real, Imag], strategy) if (strategy !== free) return math.complex.resolve([Real, Imag], strategy)
const cplx = math.complex.resolve([Real, Imag], full) const cplx = math.complex.resolve([Real, Imag], full)
const prune = math.pruneImaginary.resolve(cplx.returns, full) const prune = math.pruneImaginary.resolve(ReturnType(cplx), full)
return ReturnsAs(prune, (r, m) => prune(cplx(r, m))) return ReturnsAs(prune, (r, m) => prune(cplx(r, m)))
} }
@ -17,7 +17,8 @@ export const promoteUnary = (name, overrideStrategy) => match(
(math, C, strategy) => { (math, C, strategy) => {
const compOp = math.resolve(name, C.Component, full) const compOp = math.resolve(name, C.Component, full)
if (overrideStrategy) strategy = overrideStrategy if (overrideStrategy) strategy = overrideStrategy
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns) const NewComp = ReturnType(compOp)
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im))) return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im)))
}) })
@ -32,7 +33,8 @@ export const promoteBinary = name => match(
[Complex, Complex], [Complex, Complex],
(math, [W, Z], strategy) => { (math, [W, Z], strategy) => {
const compOp = math.resolve(name, [W.Component, Z.Component], full) const compOp = math.resolve(name, [W.Component, Z.Component], full)
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns) const NewComp = ReturnType(compOp)
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
return ReturnsAs( return ReturnsAs(
cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im))) cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im)))
}) })

View file

@ -1,5 +1,7 @@
import {Complex} from './Complex.js' import {Complex} from './Complex.js'
import {OneOf, Returns, ReturnTyping, TypeOfTypes} from "#core/Type.js" import {
OneOf, Returns, ReturnType, ReturnTyping, TypeOfTypes
} from "#core/Type.js"
import {Any, match} from "#core/TypePatterns.js" import {Any, match} from "#core/TypePatterns.js"
import {BooleanT} from '#boolean/BooleanT.js' import {BooleanT} from '#boolean/BooleanT.js'
import {NumberT} from '#number/NumberT.js' import {NumberT} from '#number/NumberT.js'
@ -32,7 +34,7 @@ export const complex = [
export const arg = // [ // enable when we have atan2 in mathjs export const arg = // [ // enable when we have atan2 in mathjs
// match(Complex, (math, C) => { // match(Complex, (math, C) => {
// const re = math.re.resolve(C) // const re = math.re.resolve(C)
// const R = re.returns // const R = ReturnType(re)
// const im = math.im.resolve(C) // const im = math.im.resolve(C)
// const abs = math.abs.resolve(C) // const abs = math.abs.resolve(C)
// const atan2 = math.atan2.resolve([R, R], conservative) // const atan2 = math.atan2.resolve([R, R], conservative)
@ -54,11 +56,11 @@ export const associate = match(
} }
const eq = math.equal.resolve([W, Z], full) const eq = math.equal.resolve([W, Z], full)
const neg = math.negate.resolve(Z, full) const neg = math.negate.resolve(Z, full)
const eqN = math.equal.resolve([W, neg.returns], full) const eqN = math.equal.resolve([W, ReturnType(neg)], full)
const mult = math.multiply.resolve([Z, Z], full) const mult = math.multiply.resolve([Z, Z], full)
const eqM = math.equal.resolve([W, mult.returns], full) const eqM = math.equal.resolve([W, ReturnType(mult)], full)
const negM = math.negate.resolve(mult.returns, full) const negM = math.negate.resolve(ReturnType(mult), full)
const eqNM = math.equal.resolve([W, negM.returns], full) const eqNM = math.equal.resolve([W, ReturnType(negM)], full)
const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component)) const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component))
return Returns(BooleanT, (w, z) => { return Returns(BooleanT, (w, z) => {
if (eq(w, z) || eqN(w, neg(z))) return true if (eq(w, z) || eqN(w, neg(z))) return true

View file

@ -13,12 +13,16 @@ As of this writing, the only two types required to be in a TypeDispatcher are
Undefined (the type inhabited only by `undefined`) and TypeOfTypes (the type Undefined (the type inhabited only by `undefined`) and TypeOfTypes (the type
inhabited exactly by Type objects). inhabited exactly by Type objects).
There is also a constant NotAType which is the type-world analogue of NaN for There is also a constant Unknown which is the type that is used when it is
numbers. It is occasionally used for the rare behavior that truly does not not possible in advance to determine what the type of an entity is or will
be. It is, for example, used for the occasional behavior that truly does not
return any particular type, such as the method `zero` that takes a Type and return any particular type, such as the method `zero` that takes a Type and
returns its zero element. However, it does not really work as a Type, and in returns its zero element. It is also used for the component type of instances
particular, do _not_ merge it into any TypeDispatcher -- it will disrupt the of containers, like Vector, that have inhomogeneous contents -- and therefore,
type and method resolution process. as the return type of `sum` on a `Vector(Unknown)`. However, it lacks much of
the machinery of other Type entities. In particular, do _not_ attempt to merge
Unknown as a type into any TypeDispatcher -- it will disrupt the type and
method resolution process.
## Core methods ## Core methods

View file

@ -66,8 +66,8 @@ export const Undefined = new Type(
t => typeof t === 'undefined', t => typeof t === 'undefined',
{zero: undefined, one: undefined, nan: undefined}) {zero: undefined, one: undefined, nan: undefined})
export const TypeOfTypes = new Type(t => t instanceof Type) export const TypeOfTypes = new Type(t => t instanceof Type)
export const NotAType = new Type(() => true) // Danger, do not merge! export const Unknown = new Type(() => true) // Danger, do not merge!
NotAType._doNotMerge = true Unknown._doNotMerge = true
const unionDirectory = new ArrayKeyedMap() // make sure only one of each union const unionDirectory = new ArrayKeyedMap() // make sure only one of each union
export const OneOf = (...types) => { export const OneOf = (...types) => {
@ -101,7 +101,8 @@ export const OneOf = (...types) => {
return unionDirectory.get(typeList) return unionDirectory.get(typeList)
} }
export const Returns = (type, f) => (f.returns = type, f) export const Returns = (type, f) => (f.returns = type, f.isBehavior = true, f)
export const ReturnType = f => f.returns ?? Unknown
export const whichType = typs => Returns(TypeOfTypes, item => { export const whichType = typs => Returns(TypeOfTypes, item => {
for (const type of Object.values(typs)) { for (const type of Object.values(typs)) {

View file

@ -3,7 +3,9 @@ import ArrayKeyedMap from 'array-keyed-map'
import {ResolutionError, isPlainFunction, isPlainObject} from './helpers.js' import {ResolutionError, isPlainFunction, isPlainObject} from './helpers.js'
import {Implementations, ImplementationsGenerator} from './Implementations.js' import {Implementations, ImplementationsGenerator} from './Implementations.js'
import {bootstrapTypes} from './type.js' import {bootstrapTypes} from './type.js'
import {Returns, ReturnTyping, whichType, Type} from './Type.js' import {
Returns, ReturnType, ReturnTyping, Unknown, Type, whichType
} from './Type.js'
import { import {
matched, needsCollection, Passthru, Matcher, match matched, needsCollection, Passthru, Matcher, match
} from './TypePatterns.js' } from './TypePatterns.js'
@ -253,7 +255,7 @@ export class TypeDispatcher {
if ('actual' in template) { // incorporate conversion if ('actual' in template) { // incorporate conversion
let convert = template.convertor let convert = template.convertor
// Check if it's a factory: // Check if it's a factory:
if (!convert.returns) convert = convert(this, template.actual) if (!convert.isBehavior) convert = convert(this, template.actual)
extractor = args => convert(args[from]) extractor = args => convert(args[from])
} }
return state ? extractor : args => [extractor(args)] return state ? extractor : args => [extractor(args)]
@ -273,6 +275,14 @@ export class TypeDispatcher {
throw new ReferenceError(`no method or value for key '${key}'`) throw new ReferenceError(`no method or value for key '${key}'`)
} }
if (!Array.isArray(types)) types = [types] if (!Array.isArray(types)) types = [types]
if (types.some(T => T === Unknown)) {
if (!strategy) return this[key]
const thisTypeOf = whichType(this.types)
return (...args) => {
const types = args.map(thisTypeOf)
return this.resolve(key, types, strategy)(...args)
}
}
if (!strategy) { if (!strategy) {
// Avoid recursing on obtaining config // Avoid recursing on obtaining config
if (key === 'config') strategy = ReturnTyping.free if (key === 'config') strategy = ReturnTyping.free
@ -380,12 +390,7 @@ export class TypeDispatcher {
finalBehavior = theBehavior finalBehavior = theBehavior
if (typeof theBehavior === 'function') { if (typeof theBehavior === 'function') {
const returning = theBehavior.returns const returning = ReturnType(theBehavior)
if (!returning) {
throw new TypeError(
`No return type specified for ${key} on ${types} with`
+ ` return typing ${ReturnTyping.name(strategy)}`)
}
if (needsCollection(template)) { if (needsCollection(template)) {
// have to wrap the behavior to collect the actual arguments // have to wrap the behavior to collect the actual arguments
// in the way corresponding to the template. Generating that // in the way corresponding to the template. Generating that

View file

@ -1,4 +1,4 @@
import {Type, Undefined} from './Type.js' import {ReturnType, Type, Undefined, Unknown} from './Type.js'
import {isPlainFunction} from './helpers.js' import {isPlainFunction} from './helpers.js'
export class TypePattern { export class TypePattern {
@ -189,8 +189,10 @@ export const matched = (template, math) => {
} }
if (template.matched) { if (template.matched) {
let convert = template.convertor let convert = template.convertor
if (!convert.returns) convert = convert(math, template.actual) if (!convert.isBehavior) convert = convert(math, template.actual)
return convert.returns || template.matched const ConvertsTo = ReturnType(convert)
if (ConvertsTo !== Unknown) return ConvertsTo
return template.matched
} }
return template return template
} }

View file

@ -2,7 +2,7 @@ import assert from 'assert'
import math from '#nanomath' import math from '#nanomath'
import {NumberT} from '#number/NumberT.js' import {NumberT} from '#number/NumberT.js'
import {Returns, ReturnTyping} from '../Type.js' import {Returns, ReturnType, ReturnTyping} from '../Type.js'
import {isPlainFunction} from '../helpers.js' import {isPlainFunction} from '../helpers.js'
describe('Core types', () => { describe('Core types', () => {
@ -36,7 +36,7 @@ describe('Core types', () => {
it('provides a return-value labeling', () => { it('provides a return-value labeling', () => {
const labeledF = Returns(math.types.Undefined, () => undefined) const labeledF = Returns(math.types.Undefined, () => undefined)
assert.strictEqual(typeof labeledF, 'function') assert.strictEqual(typeof labeledF, 'function')
assert.strictEqual(labeledF.returns, math.types.Undefined) assert.strictEqual(ReturnType(labeledF), math.types.Undefined)
assert(isPlainFunction(labeledF)) assert(isPlainFunction(labeledF))
}) })

View file

@ -6,7 +6,7 @@ import * as numbers from '#number/all.js'
import {NumberT} from '#number/NumberT.js' import {NumberT} from '#number/NumberT.js'
import {ResolutionError} from "#core/helpers.js" import {ResolutionError} from "#core/helpers.js"
import {match, Any} from "#core/TypePatterns.js" import {match, Any} from "#core/TypePatterns.js"
import {NotAType, Returns, ReturnTyping} from "#core/Type.js" import {Returns, ReturnType, ReturnTyping, Unknown} from "#core/Type.js"
import {plain} from "#number/helpers.js" import {plain} from "#number/helpers.js"
describe('TypeDispatcher', () => { describe('TypeDispatcher', () => {
@ -18,7 +18,7 @@ describe('TypeDispatcher', () => {
const {NumberT, TypeOfTypes, Undefined} = incremental.types const {NumberT, TypeOfTypes, Undefined} = incremental.types
assert(NumberT.test(7)) assert(NumberT.test(7))
assert.strictEqual(incremental.add(-1.5, 0.5), -1) assert.strictEqual(incremental.add(-1.5, 0.5), -1)
assert.throws(() => incremental.add(7, undefined), ResolutionError) assert.throws(() => incremental.add(7, NumberT), ResolutionError)
// Make Undefined act like zero: // Make Undefined act like zero:
incremental.merge({add: [ incremental.merge({add: [
match([Undefined, Any], (_m, [_U, T]) => Returns(T, (_a, b) => b)), match([Undefined, Any], (_m, [_U, T]) => Returns(T, (_a, b) => b)),
@ -26,11 +26,11 @@ describe('TypeDispatcher', () => {
]}) ]})
assert.strictEqual(incremental.add(7, undefined), 7) assert.strictEqual(incremental.add(7, undefined), 7)
assert.strictEqual( assert.strictEqual(
incremental.resolve('add', [TypeOfTypes, Undefined]).returns, ReturnType(incremental.resolve('add', [TypeOfTypes, Undefined])),
TypeOfTypes) TypeOfTypes)
assert.strictEqual(incremental.add(undefined, -3.25), -3.25) assert.strictEqual(incremental.add(undefined, -3.25), -3.25)
assert.strictEqual( assert.strictEqual(
incremental.add.resolve([Undefined, NumberT]).returns, ReturnType(incremental.add.resolve([Undefined, NumberT])),
NumberT) NumberT)
// Oops, changed my mind ;-), make it work like NaN with numbers: // Oops, changed my mind ;-), make it work like NaN with numbers:
const alwaysNaN = Returns(NumberT, () => NaN) const alwaysNaN = Returns(NumberT, () => NaN)
@ -40,7 +40,7 @@ describe('TypeDispatcher', () => {
]}) ]})
assert(isNaN(incremental.add(undefined, -3.25))) assert(isNaN(incremental.add(undefined, -3.25)))
assert.strictEqual( assert.strictEqual(
incremental.add.resolve([Undefined, NumberT]).returns, ReturnType(incremental.add.resolve([Undefined, NumberT])),
NumberT) NumberT)
assert.strictEqual(incremental.isnan(NaN), 1) assert.strictEqual(incremental.isnan(NaN), 1)
incremental.merge(booleans) incremental.merge(booleans)
@ -68,9 +68,9 @@ describe('TypeDispatcher', () => {
assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT])) assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT]))
assert.strictEqual(bgn.negate(true), -2) assert.strictEqual(bgn.negate(true), -2)
}) })
it('disallows merging NotAType', () => { it('disallows merging Unknown', () => {
const doomed = new TypeDispatcher() const doomed = new TypeDispatcher()
assert.throws(() => doomed.merge({NaT: NotAType}), TypeError) assert.throws(() => doomed.merge({Unknown}), TypeError)
}) })
it('supports generic types', () => { it('supports generic types', () => {
assert.throws(() => NumberT(0), TypeError) assert.throws(() => NumberT(0), TypeError)

View file

@ -1,15 +1,23 @@
import {NotAType, Returns, TypeOfTypes} from '#core/Type.js' import {Returns, TypeOfTypes, Unknown} from '#core/Type.js'
import {match,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 haszero = [
match(Any, (math, T) => {
const answer = math.haszero(T)
return Returns(math.typeOf(answer), () => answer)
}),
match(TypeOfTypes, boolnum(T => 'zero' in T))
]
export const zero = [ export const zero = [
match(Any, (math, T) => { match(Any, (math, T) => {
const z = math.zero(T) const z = math.zero(T)
return Returns(T, () => z) return Returns(T, () => z)
}), }),
match(TypeOfTypes, Returns(NotAType, t => { match(TypeOfTypes, Returns(Unknown, T => {
if ('zero' in t) return t.zero if ('zero' in T) return T.zero
throw new RangeError(`type '${t}' has no zero element`) throw new RangeError(`type '${T}' has no zero element`)
})) }))
] ]
@ -18,10 +26,10 @@ export const one = [
const unit = math.one(T) const unit = math.one(T)
return Returns(T, () => unit) return Returns(T, () => unit)
}), }),
match(TypeOfTypes, Returns(NotAType, t => { match(TypeOfTypes, Returns(Unknown, T => {
if ('one' in t) return t.one if ('one' in T) return T.one
throw new RangeError( throw new RangeError(
`type '${t}' has no unit element designated as "one"`) `type '${T}' has no unit element designated as "one"`)
})) }))
] ]
@ -30,7 +38,7 @@ export const hasnan = [
const answer = math.hasnan(T) const answer = math.hasnan(T)
return Returns(math.typeOf(answer), () => answer) return Returns(math.typeOf(answer), () => answer)
}), }),
match(TypeOfTypes, boolnum(t => 'nan' in t)) match(TypeOfTypes, boolnum(T => 'nan' in T))
] ]
export const nan = [ export const nan = [
@ -38,9 +46,9 @@ export const nan = [
const notanum = math.nan(T) const notanum = math.nan(T)
return Returns(T, () => notanum) return Returns(T, () => notanum)
}), }),
match(TypeOfTypes, Returns(NotAType, t => { match(TypeOfTypes, Returns(Unknown, T => {
if ('nan' in t) return t.nan if ('nan' in T) return T.nan
throw new RangeError( throw new RangeError(
`type '${t}' has no "not a number" element`) `type '${T}' has no "not a number" element`)
})) }))
] ]

View file

@ -1,6 +1,6 @@
import assert from 'assert' import assert from 'assert'
import math from '#nanomath' import math from '#nanomath'
import {ReturnTyping} from '#core/Type.js' import {ReturnType, ReturnTyping} from '#core/Type.js'
const {Complex, NumberT} = math.types const {Complex, NumberT} = math.types
@ -8,7 +8,7 @@ describe('generic arithmetic', () => {
it('squares anything', () => { it('squares anything', () => {
const sq = math.square const sq = math.square
assert.strictEqual(sq(7), 49) assert.strictEqual(sq(7), 49)
assert.strictEqual(math.square.resolve([NumberT]).returns, NumberT) assert.strictEqual(ReturnType(math.square.resolve([NumberT])), NumberT)
assert.deepStrictEqual(sq(math.complex(3, 4)), math.complex(-7, 24)) assert.deepStrictEqual(sq(math.complex(3, 4)), math.complex(-7, 24))
const eyes = math.complex(0, 2) const eyes = math.complex(0, 2)
assert.strictEqual(sq(eyes), -4) assert.strictEqual(sq(eyes), -4)

View file

@ -1,18 +1,20 @@
import {ReturnsAs} from './helpers.js' import {ReturnsAs} from './helpers.js'
import {Returns, ReturnTyping} from '#core/Type.js' import {Returns, ReturnType, ReturnTyping} from '#core/Type.js'
import {match, Any} from '#core/TypePatterns.js' import {match, Any} from '#core/TypePatterns.js'
export const norm = match(Any, (math, T) => { export const norm = match(Any, (math, T) => {
const normsq = math.normsq.resolve(T) const normsq = math.normsq.resolve(T)
const sqrt = math.sqrt.resolve(normsq.returns, ReturnTyping.conservative) const sqrt = math.sqrt.resolve(
ReturnType(normsq), ReturnTyping.conservative)
return ReturnsAs(sqrt, t => sqrt(normsq(t))) return ReturnsAs(sqrt, t => sqrt(normsq(t)))
}) })
export const abs = norm // coincide for most types (scalars) export const abs = norm // coincide for most types (scalars)
export const conj = match(Any, (_math, T) => Returns(T, t => t)) export const conj = match(Any, (_math, T) => Returns(T, t => t))
export const square = match(Any, (math, T, strategy) => { export const square = match(Any, (math, T, strategy) => {
const mult = math.multiply.resolve([T, T], strategy) const mult = math.multiply.resolve([T, T], strategy)
return Returns(mult.returns, t => mult(t, t)) return ReturnsAs(mult, t => mult(t, t))
}) })

View file

@ -1 +1,3 @@
export const ReturnsAs = (g, f) => (f.returns = g.returns, f) import {Returns, ReturnType} from '#core/Type.js'
export const ReturnsAs = (g, f) => Returns(ReturnType(g), f)

View file

@ -1,6 +1,6 @@
import {ReturnsAs} from './helpers.js' import {ReturnsAs} from './helpers.js'
import {OneOf, Returns} from '#core/Type.js' import {OneOf, Returns, ReturnType} from '#core/Type.js'
import {Any, Multiple, match} from '#core/TypePatterns.js' import {Any, Multiple, match} from '#core/TypePatterns.js'
import {boolnum} from '#number/helpers.js' import {boolnum} from '#number/helpers.js'
@ -18,7 +18,7 @@ export const and = [
}), }),
match([Any, Any, Any, Multiple(Any)], (math, [T, U, V, Rest]) => { match([Any, Any, Any, Multiple(Any)], (math, [T, U, V, Rest]) => {
const andRest = math.and.resolve([U, V, ...Rest]) const andRest = math.and.resolve([U, V, ...Rest])
const andFirst = math.and.resolve([T, andRest.returns]) const andFirst = math.and.resolve([T, ReturnType(andRest)])
return ReturnsAs( return ReturnsAs(
andFirst, (t, u, v, rest) => andFirst(t, andRest(u, v, ...rest))) andFirst, (t, u, v, rest) => andFirst(t, andRest(u, v, ...rest)))
}) })
@ -33,7 +33,7 @@ export const or = [
}), }),
match([Any, Any, Any, Multiple(Any)], (math, [T, U, V, Rest]) => { match([Any, Any, Any, Multiple(Any)], (math, [T, U, V, Rest]) => {
const orRest = math.or.resolve([U, V, ...Rest]) const orRest = math.or.resolve([U, V, ...Rest])
const orFirst = math.and.resolve([T, orRest.returns]) const orFirst = math.and.resolve([T, ReturnType(orRest)])
return ReturnsAs( return ReturnsAs(
orFirst, (t, u, v, rest) => orFirst(t, orRest(u, v, ...rest))) orFirst, (t, u, v, rest) => orFirst(t, orRest(u, v, ...rest)))
}) })

View file

@ -1,5 +1,5 @@
import {ReturnsAs} from './helpers.js' import {ReturnsAs} from './helpers.js'
import {Returns, ReturnTyping} from '#core/Type.js' import {Returns, ReturnType, ReturnTyping} from '#core/Type.js'
import {Any, Passthru, match, 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'
@ -82,15 +82,15 @@ export const sign = match(Any, (math, T) => {
export const unequal = match(Passthru, (math, types) => { export const unequal = match(Passthru, (math, types) => {
const eq = math.equal.resolve(types) const eq = math.equal.resolve(types)
const not = math.not.resolve(eq.returns) const not = math.not.resolve(ReturnType(eq))
return ReturnsAs(not, (...args) => not(eq(...args))) return ReturnsAs(not, (...args) => not(eq(...args)))
}) })
export const larger = match([Any, Any], (math, [T, U]) => { export const larger = match([Any, Any], (math, [T, U]) => {
const eq = math.equal.resolve([T, U]) const eq = math.equal.resolve([T, U])
const bigger = math.exceeds.resolve([T, U]) const bigger = math.exceeds.resolve([T, U])
const not = math.not.resolve(eq.returns) const not = math.not.resolve(ReturnType(eq))
const and = math.and.resolve([not.returns, bigger.returns]) const and = math.and.resolve([ReturnType(not), ReturnType(bigger)])
return ReturnsAs(and, (t, u) => and(not(eq(t, u)), bigger(t, u))) return ReturnsAs(and, (t, u) => and(not(eq(t, u)), bigger(t, u)))
}) })
@ -103,21 +103,21 @@ export const isPositive = match(Any, (math, T) => {
export const largerEq = match([Any, Any], (math, [T, U]) => { export const largerEq = match([Any, Any], (math, [T, U]) => {
const eq = math.equal.resolve([T, U]) const eq = math.equal.resolve([T, U])
const bigger = math.exceeds.resolve([T, U]) const bigger = math.exceeds.resolve([T, U])
const or = math.or.resolve([eq.returns, bigger.returns]) const or = math.or.resolve([ReturnType(eq), ReturnType(bigger)])
return ReturnsAs(or, (t, u) => or(eq(t, u), bigger(t, u))) return ReturnsAs(or, (t, u) => or(eq(t, u), bigger(t, u)))
}) })
export const smaller = match([Any, Any], (math, [T, U]) => { export const smaller = match([Any, Any], (math, [T, U]) => {
const eq = math.equal.resolve([T, U]) const eq = math.equal.resolve([T, U])
const bigger = math.exceeds.resolve([U, T]) const bigger = math.exceeds.resolve([U, T])
const not = math.not.resolve(eq.returns) const not = math.not.resolve(ReturnType(eq))
const and = math.and.resolve([not.returns, bigger.returns]) const and = math.and.resolve([ReturnType(not), ReturnType(bigger)])
return ReturnsAs(and, (t, u) => and(not(eq(t, u)), bigger(u, t))) return ReturnsAs(and, (t, u) => and(not(eq(t, u)), bigger(u, t)))
}) })
export const smallerEq = match([Any, Any], (math, [T, U]) => { export const smallerEq = match([Any, Any], (math, [T, U]) => {
const eq = math.equal.resolve([T, U]) const eq = math.equal.resolve([T, U])
const bigger = math.exceeds.resolve([U, T]) const bigger = math.exceeds.resolve([U, T])
const or = math.or.resolve([eq.returns, bigger.returns]) const or = math.or.resolve([ReturnType(eq), ReturnType(bigger)])
return ReturnsAs(or, (t, u) => or(eq(t, u), bigger(u, t))) return ReturnsAs(or, (t, u) => or(eq(t, u), bigger(u, t)))
}) })

View file

@ -1,6 +1,6 @@
import {plain} from './helpers.js' import {plain} from './helpers.js'
import {NumberT} from './NumberT.js' import {NumberT} from './NumberT.js'
import {OneOf, Returns, ReturnTyping} from '#core/Type.js' import {OneOf, Returns, ReturnTyping, Undefined} from '#core/Type.js'
import {match} from '#core/TypePatterns.js' import {match} from '#core/TypePatterns.js'
import {Complex} from '#complex/Complex.js' import {Complex} from '#complex/Complex.js'
@ -9,7 +9,11 @@ const {conservative, full} = ReturnTyping
export const abs = plain(Math.abs) export const abs = plain(Math.abs)
export const norm = abs export const norm = abs
export const normsq = plain(a => a*a) export const normsq = plain(a => a*a)
export const add = plain((a, b) => a + b) export const add = [
plain((a, b) => a + b),
match([Undefined, NumberT], Returns(NumberT, () => NaN)),
match([NumberT, Undefined], Returns(NumberT, () => NaN))
]
export const divide = plain((a, b) => a / b) export const divide = plain((a, b) => a / b)
export const cbrt = plain(a => { export const cbrt = plain(a => {
if (a === 0) return a if (a === 0) return a

View file

@ -1,4 +1,4 @@
import {NotAType, Type} from '#core/Type.js' import {Type, Unknown} from '#core/Type.js'
const isVector = v => Array.isArray(v) const isVector = v => Array.isArray(v)
@ -12,7 +12,7 @@ export const Vector = new Type(isVector, {
if ('zero' in CompType) typeOptions.zero = [CompType.zero] if ('zero' in CompType) typeOptions.zero = [CompType.zero]
const vectorCompType = new Type(specTest, typeOptions) const vectorCompType = new Type(specTest, typeOptions)
vectorCompType.Component = CompType vectorCompType.Component = CompType
vectorCompType.vector = true vectorCompType.vectorDepth = (CompType.vectorDepth ?? 0) + 1
return vectorCompType return vectorCompType
} }
// Wrapping a generic type in Vector // Wrapping a generic type in Vector
@ -37,12 +37,12 @@ export const Vector = new Type(isVector, {
} }
}) })
}, },
specializesTo: VT => VT.vector, specializesTo: VT => 'vectorDepth' in VT && VT.vectorDepth > 0,
refine(v, typer) { refine(v, typer) {
if (!v.length) return this.specialize(NotAType) // what else could we do? if (!v.length) return this.specialize(Unknown) // what else could we do?
const eltTypes = v.map(elt => typer(elt)) const eltTypes = v.map(elt => typer(elt))
let compType = eltTypes[0] let compType = eltTypes[0]
if (eltTypes.some(T => T !== compType)) compType = NotAType if (eltTypes.some(T => T !== compType)) compType = Unknown
return this.specialize(compType) return this.specialize(compType)
} }
}) })

View file

@ -1,6 +1,6 @@
import assert from 'assert' import assert from 'assert'
import {Vector} from '../Vector.js' import {Vector} from '../Vector.js'
import {NotAType} from '#core/Type.js' import {Unknown} from '#core/Type.js'
import math from '#nanomath' import math from '#nanomath'
describe('Vector Type', () => { describe('Vector Type', () => {
@ -14,7 +14,7 @@ describe('Vector Type', () => {
const cplx = math.complex const cplx = math.complex
assert.strictEqual(typ([3, 4]), Vector(NumberT)) assert.strictEqual(typ([3, 4]), Vector(NumberT))
assert.strictEqual(typ([true, false]), Vector(BooleanT)) assert.strictEqual(typ([true, false]), Vector(BooleanT))
assert.strictEqual(typ([3, false]), Vector(NotAType)) assert.strictEqual(typ([3, false]), Vector(Unknown))
assert.strictEqual(typ([cplx(3, 4), cplx(5)]), Vector(Complex(NumberT))) assert.strictEqual(typ([cplx(3, 4), cplx(5)]), Vector(Complex(NumberT)))
assert.strictEqual(typ([[3, 4], [5]]), Vector(Vector(NumberT))) assert.strictEqual(typ([[3, 4], [5]]), Vector(Vector(NumberT)))
}) })

View file

@ -0,0 +1,33 @@
import assert from 'assert'
import math from '#nanomath'
describe('Vector arithmetic functions', () => {
const cplx = math.complex
const cV = [cplx(3, 4), cplx(4, -3), cplx(-3, -4)]
it('distributes abs elementwise', () => {
const abs = math.abs
assert.deepStrictEqual(abs([-3, 4, -5]), [3, 4, 5])
assert.deepStrictEqual(abs(cV), [5, 5, 5])
assert.deepStrictEqual(abs([true, -4, cplx(4, 3)]), [1, 4, 5])
})
it('computes the norm of a vector or matrix', () => {
const norm = math.norm
assert.strictEqual(norm([-3, 4, -5]), Math.sqrt(50))
assert.strictEqual(norm([[1, 2], [4, 2]]), 5)
assert.strictEqual(norm(cV), Math.sqrt(75))
})
it('adds vectors and matrices', () => {
const add = math.add
assert.deepStrictEqual(add([-3, 4, -5], [8, 0, 2]), [5, 4, -3])
assert.deepStrictEqual(
add([[1, 2], [4, 2]], [[0, -1], [3, -4]]), [[1, 1], [7, -2]])
assert.deepStrictEqual(
add([[1, 2], [4, 2]], [0, -1]), [[1, 1], [4, 1]])
})
it('computes the sum of a vector', () => {
const sum = math.sum
assert.strictEqual(sum([-3, 4, -5]), -4)
assert.deepStrictEqual(sum(cV), cplx(4, -3))
assert.strictEqual(sum([4, true, -2]), 3)
})
})

View file

@ -1,5 +1,5 @@
import assert from 'assert' import assert from 'assert'
import {NotAType} from '#core/Type.js' import {ReturnType, Unknown} from '#core/Type.js'
import math from '#nanomath' import math from '#nanomath'
describe('Vector type functions', () => { describe('Vector type functions', () => {
@ -8,9 +8,11 @@ describe('Vector type functions', () => {
const {BooleanT, NumberT, Vector} = math.types const {BooleanT, NumberT, Vector} = math.types
assert.deepStrictEqual(vec(3, 4, 5), [3, 4, 5]) assert.deepStrictEqual(vec(3, 4, 5), [3, 4, 5])
assert.strictEqual( assert.strictEqual(
vec.resolve([NumberT, NumberT, NumberT]).returns, Vector(NumberT)) ReturnType(vec.resolve([NumberT, NumberT, NumberT])),
Vector(NumberT))
assert.deepStrictEqual(vec(3, true), [3, true]) assert.deepStrictEqual(vec(3, true), [3, true])
assert.strictEqual( assert.strictEqual(
vec.resolve([NumberT, BooleanT]).returns, Vector(NotAType)) ReturnType(vec.resolve([NumberT, BooleanT])),
Vector(Unknown))
}) })
}) })

View file

@ -1,4 +1,5 @@
export * as typeDefinition from './Vector.js' export * as typeDefinition from './Vector.js'
export * as arithmetic from './arithmetic.js'
export * as logical from './logical.js' export * as logical from './logical.js'
export * as relational from './relational.js' export * as relational from './relational.js'
export * as type from './type.js' export * as type from './type.js'

33
src/vector/arithmetic.js Normal file
View file

@ -0,0 +1,33 @@
import {promoteBinary, promoteUnary} from './helpers.js'
import {Vector} from './Vector.js'
import {ReturnType} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js'
export const normsq = match(Vector, (math, V) => {
const compNormsq = math.normsq.resolve(V.Component)
const sum = math.sum.resolve(Vector(ReturnType(compNormsq)))
return ReturnsAs(sum, v => sum(v.map(compNormsq)))
})
// abs and norm differ only on Vector (and perhaps other collections) --
// norm computes overall by the generic formula, whereas abs distributes
// elementwise:
export const abs = promoteUnary('abs')
export const add = promoteBinary('add')
export const sum = match(Vector, (math, V) => {
const add = math.add.resolve([V.Component, V.Component])
const haszero = math.haszero(V.Component)
const zero = haszero ? math.zero(V.Component) : undefined
return ReturnsAs(add, v => {
if (v.length === 0) {
if (haszero) return zero
throw new TypeError(`Can't sum empty ${V}: no zero element`)
}
let ix = 0
let retval = v[ix]
while (++ix < v.length) retval = add(retval, v[ix])
return retval
})
})

View file

@ -1,61 +1,44 @@
import {Vector} from './Vector.js' import {Vector} from './Vector.js'
import {NotAType, Returns, Undefined} from '#core/Type.js' import {Returns, ReturnType, Undefined} from '#core/Type.js'
import {Any, match} from '#core/TypePatterns.js' import {Any, match} from '#core/TypePatterns.js'
export const promoteUnary = name => match(Vector, (math, V, strategy) => { export const promoteUnary = name => match(Vector, (math, V, strategy) => {
if (V.Component === NotAType) {
// have to resolve element by element :-(
return Returns(V, v => v.map(
elt => math.resolve(name, math.typeOf(elt), strategy)(elt)))
}
const compOp = math.resolve(name, V.Component, strategy) const compOp = math.resolve(name, V.Component, strategy)
return Returns(Vector(compOp.returns), v => v.map(elt => compOp(elt))) return Returns(Vector(ReturnType(compOp)), v => v.map(elt => compOp(elt)))
}) })
export const promoteBinary = name => [ export const promoteBinary = name => [
match([Vector, Any], (math, [V, E], strategy) => { match([Vector, Any], (math, [V, E], strategy) => {
const VComp = V.Component const compOp = math.resolve(name, [V.Component, E], strategy)
if (VComp === NotAType) {
return Returns(V, (v, e) => v.map(
f => math.resolve(name, [math.typeOf(f), E], strategy)(f, e)))
}
const compOp = math.resolve(name, [VComp, E], strategy)
return Returns( return Returns(
Vector(compOp.returns), (v, e) => v.map(f => compOp(f, e))) Vector(ReturnType(compOp)), (v, e) => v.map(f => compOp(f, e)))
}), }),
match([Any, Vector], (math, [E, V], strategy) => { match([Any, Vector], (math, [E, V], strategy) => {
const VComp = V.Component const compOp = math.resolve(name, [E, V.Component], strategy)
if (VComp === NotAType) {
return Returns(V, (e, v) => v.map(
f => math.resolve(name, [E, math.typeOf(f)], strategy)(e, f)))
}
const compOp = math.resolve(name, [E, VComp], strategy)
return Returns( return Returns(
Vector(compOp.returns, (e, v) => v.map(f => compOp(e, f)))) Vector(ReturnType(compOp)), (e, v) => v.map(f => compOp(e, f)))
}), }),
match([Vector, Vector], (math, [V, W], strategy) => { match([Vector, Vector], (math, [V, W], strategy) => {
const VComp = V.Component const VComp = V.Component
const WComp = W.Component const WComp = W.Component
let compOp // special case: if the vector nesting depths do not match,
let opNoV // we operate between the elements of the deeper one and the entire
let opNoW // more shallow one:
let ReturnType if (V.vectorDepth > W.vectorDepth) {
if (VComp === NotAType || WComp === NotAType) { const compOp = math.resolve(name, [VComp, W], strategy)
const typ = math.typeOf return Returns(
compOp = (v, w) => { Vector(ReturnType(compOp)), (v, w) => v.map(f => compOp(f, w)))
return math.resolve(name, [typ(v), typ(w)], strategy)(v, w)
}
opNoV = compOp
opNoW = compOp
ReturnType = Vector(NotAType)
} else {
compOp = math.resolve(name, [VComp, WComp], strategy)
opNoV = math.resolve(name, [Undefined, WComp], strategy)
opNoW = math.resolve(name, [VComp, Undefined], strategy)
ReturnType = Vector(compOp.returns)
} }
if (V.vectorDepth < W.vectorDepth) {
const compOp = math.resolve(name, [V, WComp], strategy)
return Returns(
Vector(ReturnType(compOp)), (v, w) => w.map(f => compOp(v, f)))
}
const compOp = math.resolve(name, [VComp, WComp], strategy)
const opNoV = math.resolve(name, [Undefined, WComp], strategy)
const opNoW = math.resolve(name, [VComp, Undefined], strategy)
return Returns( return Returns(
ReturnType, Vector(ReturnType(compOp)),
(v, w) => { (v, w) => {
const vInc = Number(v.length > 1) const vInc = Number(v.length > 1)
const wInc = Number(w.length >= v.length || w.length > 1) const wInc = Number(w.length >= v.length || w.length > 1)

View file

@ -1,7 +1,7 @@
import {Vector} from './Vector.js' import {Vector} from './Vector.js'
import {promoteBinary} from './helpers.js' import {promoteBinary} from './helpers.js'
import {NotAType, Returns, Undefined} from '#core/Type.js' import {Unknown, Returns, ReturnType, Undefined} from '#core/Type.js'
import {Any, Optional, match} from '#core/TypePatterns.js' import {Any, Optional, match} from '#core/TypePatterns.js'
import {BooleanT} from '#boolean/BooleanT.js' import {BooleanT} from '#boolean/BooleanT.js'
@ -9,11 +9,6 @@ export const deepEqual = [
match([Vector, Any], Returns(BooleanT, () => false)), match([Vector, Any], Returns(BooleanT, () => false)),
match([Any, Vector], Returns(BooleanT, () => false)), match([Any, Vector], Returns(BooleanT, () => false)),
match([Vector, Vector], (math, [V, W]) => { match([Vector, Vector], (math, [V, W]) => {
if (V.Component === NotAType || W.Component === NotAType) {
return Returns(BooleanT, (v, w) => v === w
|| (v.length === w.length
&& v.every((e, i) => math.deepEqual(e, w[i]))))
}
const compDeep = math.deepEqual.resolve([V.Component, W.Component]) const compDeep = math.deepEqual.resolve([V.Component, W.Component])
return Returns(BooleanT, (v,w) => v === w return Returns(BooleanT, (v,w) => v === w
|| (v.length === w.length || (v.length === w.length
@ -25,24 +20,14 @@ export const indistinguishable = [
match([Vector, Any, Optional([Any, Any])], (math, [V, E, T]) => { match([Vector, Any, Optional([Any, Any])], (math, [V, E, T]) => {
const VComp = V.Component const VComp = V.Component
if (T.length === 0) { // no tolerances if (T.length === 0) { // no tolerances
if (VComp === NotAType) {
return Returns(V, (v, e) => v.map(f => {
return math.indistinguishable.resolve([math.typeOf(f), E])(f, e)
}))
}
const same = math.indistinguishable.resolve([VComp, E]) const same = math.indistinguishable.resolve([VComp, E])
return Returns( return Returns(
Vector(same.returns), (v, e) => v.map(f => same(f, e))) Vector(ReturnType(same)), (v, e) => v.map(f => same(f, e)))
} }
const [[RT, AT]] = T const [[RT, AT]] = T
if (VComp === NotAType) {
return Returns(V, (v, e, [[rT, aT]]) => v.map(f => {
return math.indistinguishable(f, e, rT, aT)
}))
}
const same = math.indistinguishable.resolve([VComp, E, RT, AT]) const same = math.indistinguishable.resolve([VComp, E, RT, AT])
return Returns( return Returns(
Vector(same.returns), Vector(ReturnType(same)),
(v, e, [[rT, aT]]) => v.map(f => same(f, e, rT, aT))) (v, e, [[rT, aT]]) => v.map(f => same(f, e, rT, aT)))
}), }),
match([Any, Vector, Optional([Any, Any])], (math, [E, V, T]) => { match([Any, Vector, Optional([Any, Any])], (math, [E, V, T]) => {
@ -50,30 +35,20 @@ export const indistinguishable = [
// same is symmetric, even though it probably is // same is symmetric, even though it probably is
const VComp = V.Component const VComp = V.Component
if (T.length === 0) { // no tolerances if (T.length === 0) { // no tolerances
if (VComp === NotAType) {
return Returns(V, (e, v) => v.map(f => {
return math.indistinguishable.resolve([E, math.typeOf(f)])(e, f)
}))
}
const same = math.indistinguishable.resolve([E, VComp]) const same = math.indistinguishable.resolve([E, VComp])
return Returns( return Returns(
Vector(same.returns), (e, v) => v.map(f => same(e, f))) Vector(ReturnType(same)), (e, v) => v.map(f => same(e, f)))
} }
const [[RT, AT]] = T const [[RT, AT]] = T
if (VComp === NotAType) {
return Returns(V, (e, v, [[rT, aT]]) => v.map(f => {
return math.indistiguishable(e, f, rT, aT)
}))
}
const same = math.indistinguishable.resolve([E, VComp, RT, AT]) const same = math.indistinguishable.resolve([E, VComp, RT, AT])
return Returns( return Returns(
Vector(same.returns), Vector(ReturnType(same)),
(e, v, [[rT, aT]]) => v.map(f => same(e, f, rT, aT))) (e, v, [[rT, aT]]) => v.map(f => same(e, f, rT, aT)))
}), }),
match([Vector, Vector, Optional([Any, Any])], (math, [V, W, T]) => { match([Vector, Vector, Optional([Any, Any])], (math, [V, W, T]) => {
const VComp = V.Component const VComp = V.Component
const WComp = W.Component const WComp = W.Component
const inhomogeneous = VComp === NotAType || WComp === NotAType const inhomogeneous = VComp === Unknown || WComp === Unknown
let same let same
let sameNoV let sameNoV
let sameNoW let sameNoW
@ -92,7 +67,7 @@ export const indistinguishable = [
sameNoW = math.indistinguishable.resolve([VComp, Undefined, RT, AT]) sameNoW = math.indistinguishable.resolve([VComp, Undefined, RT, AT])
} }
return Returns( return Returns(
inhomogeneous ? Vector(NotAType) : Vector(same.returns), inhomogeneous ? Vector(Unknown) : Vector(ReturnType(same)),
(v, w, [tol = [0, 0]]) => { (v, w, [tol = [0, 0]]) => {
const [rT, aT] = tol const [rT, aT] = tol
const vInc = Number(v.length > 1) const vInc = Number(v.length > 1)

View file

@ -1,11 +1,11 @@
import {Vector} from './Vector.js' import {Vector} from './Vector.js'
import {NotAType, Returns} from '#core/Type.js' import {Returns, Unknown} from '#core/Type.js'
import {Any, Multiple, match} from '#core/TypePatterns.js' import {Any, Multiple, match} from '#core/TypePatterns.js'
export const vector = match(Multiple(Any), (math, [TV]) => { export const vector = match(Multiple(Any), (math, [TV]) => {
if (!TV.length) return Returns(Vector(NotAType), () => []) if (!TV.length) return Returns(Vector(Unknown), () => [])
let CompType = TV[0] let CompType = TV[0]
if (TV.some(T => T !== CompType)) CompType = NotAType if (TV.some(T => T !== CompType)) CompType = Unknown
return Returns(Vector(CompType), v => v) return Returns(Vector(CompType), v => v)
}) })