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,61 +1,44 @@
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'
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)
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 => [
match([Vector, Any], (math, [V, E], strategy) => {
const VComp = V.Component
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)
const compOp = math.resolve(name, [V.Component, E], strategy)
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) => {
const VComp = V.Component
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)
const compOp = math.resolve(name, [E, V.Component], strategy)
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) => {
const VComp = V.Component
const WComp = W.Component
let compOp
let opNoV
let opNoW
let ReturnType
if (VComp === NotAType || WComp === NotAType) {
const typ = math.typeOf
compOp = (v, 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)
// special case: if the vector nesting depths do not match,
// we operate between the elements of the deeper one and the entire
// more shallow one:
if (V.vectorDepth > W.vectorDepth) {
const compOp = math.resolve(name, [VComp, W], strategy)
return Returns(
Vector(ReturnType(compOp)), (v, w) => v.map(f => compOp(f, w)))
}
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(
ReturnType,
Vector(ReturnType(compOp)),
(v, w) => {
const vInc = Number(v.length > 1)
const wInc = Number(w.length >= v.length || w.length > 1)