feat: Implement Vector type #28
26 changed files with 238 additions and 171 deletions
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)))
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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`)
|
||||||
}))
|
}))
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)))
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)))
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)))
|
||||||
})
|
})
|
||||||
|
|
33
src/vector/__test__/arithmetic.spec.js
Normal file
33
src/vector/__test__/arithmetic.spec.js
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
|
@ -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))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
33
src/vector/arithmetic.js
Normal 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
|
||||||
|
})
|
||||||
|
})
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue