Adds all of the pocomath functions on Complex that do not depend on any unimplemented types or config properties, except quotient and roundquotient, where the design is murky. To get this working, adds some additional features: * Allows conversions to generic types, with the matched type determined from the return value of the built convertor * Adds predicate-based type patterns * Adds conversion from any non-complex type T to Complex(T) * Adds check for recursive loops in resolve (a key/signature depending on itself) Reviewed-on: #24 Co-authored-by: Glen Whitney <glen@studioinfinity.org> Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
parent
0ff00ff8cb
commit
aad62df8ac
33 changed files with 428 additions and 106 deletions
|
@ -1,6 +1,7 @@
|
||||||
import {Type} from '#core/Type.js'
|
import {Type} from '#core/Type.js'
|
||||||
|
|
||||||
export const BooleanT = new Type(n => typeof n === 'boolean', {
|
export const BooleanT = new Type(n => typeof n === 'boolean', {
|
||||||
|
typeName: 'BooleanT',
|
||||||
zero: false,
|
zero: false,
|
||||||
one: true
|
one: true
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {BooleanT} from './BooleanT.js'
|
import {BooleanT} from './BooleanT.js'
|
||||||
import {match} from '#core/helpers.js'
|
import {match} from '#core/TypePatterns.js'
|
||||||
import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
|
import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
|
||||||
import {NumberT} from '#number/NumberT.js'
|
import {NumberT} from '#number/NumberT.js'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Type} from '#core/Type.js'
|
import {Returns, Type} from '#core/Type.js'
|
||||||
import {match} from '#core/helpers.js'
|
import {match} from '#core/TypePatterns.js'
|
||||||
|
|
||||||
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z
|
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z
|
||||||
|
|
||||||
|
@ -66,6 +66,12 @@ function complexSpecialize(ComponentType) {
|
||||||
export const Complex = new Type(isComplex, {
|
export const Complex = new Type(isComplex, {
|
||||||
specialize: complexSpecialize,
|
specialize: complexSpecialize,
|
||||||
specializesTo,
|
specializesTo,
|
||||||
|
from: [match( // can promote any non-complex type T to Complex(T) as needed
|
||||||
|
// but watch out, this should be tried late, because it can preclude
|
||||||
|
// other more reasonable conversions like bool => number.
|
||||||
|
T => !T.complex,
|
||||||
|
(math, T) => Returns(Complex(T), r => math.complex(r, math.zero(T)))
|
||||||
|
)],
|
||||||
refine: function(z, typer) {
|
refine: function(z, typer) {
|
||||||
const reType = typer(z.re)
|
const reType = typer(z.re)
|
||||||
const imType = typer(z.im)
|
const imType = typer(z.im)
|
||||||
|
|
78
src/complex/__test__/arithmetic.spec.js
Normal file
78
src/complex/__test__/arithmetic.spec.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import math from '#nanomath'
|
||||||
|
import {Complex} from '../Complex.js'
|
||||||
|
import {NumberT} from '#number/NumberT.js'
|
||||||
|
|
||||||
|
const cplx = math.complex
|
||||||
|
|
||||||
|
describe('complex arithmetic operations', () => {
|
||||||
|
it('computes absquare of complex numbers', () => {
|
||||||
|
assert.strictEqual(math.absquare(cplx(3, 4)), 25)
|
||||||
|
assert.strictEqual(math.absquare(cplx(cplx(2, 3), cplx(4,5))), 54)
|
||||||
|
assert.strictEqual(math.absquare(cplx(true, true)), 2)
|
||||||
|
})
|
||||||
|
it('adds complex numbers', () => {
|
||||||
|
const z = cplx(3, 4)
|
||||||
|
const add = math.add
|
||||||
|
assert.deepStrictEqual(add(z, cplx(-1, 1)), cplx(2, 5))
|
||||||
|
assert.deepStrictEqual(add(z, cplx(true, false)), cplx(4, 4))
|
||||||
|
assert.deepStrictEqual(add(cplx(false, true), z), cplx(3, 5))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
add(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
|
||||||
|
cplx(cplx(3.5, 4.5), cplx(6, 8)))
|
||||||
|
assert.deepStrictEqual(add(z, 5), cplx(8, 4))
|
||||||
|
assert.deepStrictEqual(add(true, z), cplx(4, 4))
|
||||||
|
assert.deepStrictEqual(add(cplx(z,z), 10), cplx(cplx(13, 4), z))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4)))
|
||||||
|
})
|
||||||
|
it('conjugates complex numbers', () => {
|
||||||
|
const conj = math.conj
|
||||||
|
const z = cplx(3, 4)
|
||||||
|
assert.deepStrictEqual(conj(z), cplx(3, -4))
|
||||||
|
assert.deepStrictEqual(conj(cplx(z,z)), cplx(cplx(3, -4), cplx(-3, -4)))
|
||||||
|
})
|
||||||
|
it('divides complex numbers', () => {
|
||||||
|
const div = math.divide
|
||||||
|
const z = cplx(3, 4)
|
||||||
|
assert.deepStrictEqual(div(z, cplx(0, 1)), cplx(4, -3))
|
||||||
|
assert(math.equal(div(z, z), 1))
|
||||||
|
// should probably have a quaternion example, but it's intricate
|
||||||
|
})
|
||||||
|
it('inverts complex numbers', () => {
|
||||||
|
const inv = math.invert
|
||||||
|
assert.deepStrictEqual(inv(cplx(0, 1)), cplx(0, -1))
|
||||||
|
assert.deepStrictEqual(inv(cplx(3, 4)), cplx(3/25, -4/25))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
inv(cplx(cplx(1, 2), cplx(4, 2))),
|
||||||
|
cplx(cplx(1/25, -2/25), cplx(-4/25, -2/25)))
|
||||||
|
})
|
||||||
|
it('multiplies complex numbers', () => {
|
||||||
|
const mult = math.multiply
|
||||||
|
const z = cplx(3, 4)
|
||||||
|
assert.deepStrictEqual(mult(z, z), cplx(-7, 24))
|
||||||
|
assert(math.equal(mult(z, math.conj(z)), 25))
|
||||||
|
const q0 = cplx(cplx(1, 1), math.zero(Complex(NumberT)))
|
||||||
|
const q1 = cplx(cplx(1, 0.5), cplx(0.5, 0.75))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
mult(q0, q1), cplx(cplx(0.5, 1.5), cplx(1.25, 0.25)))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
mult(q0, cplx(cplx(2, 0.1), cplx(1, 0.1))),
|
||||||
|
cplx(cplx(1.9, 2.1), cplx(1.1, -0.9)))
|
||||||
|
})
|
||||||
|
it('subtracts complex numbers', () => {
|
||||||
|
const z = cplx(3, 4)
|
||||||
|
const sub = math.subtract
|
||||||
|
assert.deepStrictEqual(sub(z, cplx(-1, 1)), cplx(4, 3))
|
||||||
|
assert.deepStrictEqual(sub(z, cplx(true, false)), cplx(2, 4))
|
||||||
|
assert.deepStrictEqual(sub(cplx(false, true), z), cplx(-3, -3))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
|
||||||
|
cplx(cplx(2.5, 3.5), cplx(0, 0)))
|
||||||
|
assert.deepStrictEqual(sub(z, 5), cplx(-2, 4))
|
||||||
|
assert.deepStrictEqual(sub(true, z), cplx(-2, -4))
|
||||||
|
assert.deepStrictEqual(sub(cplx(z,z), 10), cplx(cplx(-7, 4), z))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
sub(cplx(z,z), cplx(10,20)), cplx(cplx(-7, 4), cplx(-17, 4)))
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,12 +0,0 @@
|
||||||
import assert from 'assert'
|
|
||||||
import math from '#nanomath'
|
|
||||||
|
|
||||||
describe('complex type operations', () => {
|
|
||||||
it('converts to number', () => {
|
|
||||||
assert.deepStrictEqual(math.complex(3), {re: 3, im: 0})
|
|
||||||
assert.deepStrictEqual(math.complex(NaN), {re: NaN, im: NaN})
|
|
||||||
assert.deepStrictEqual(math.complex(3, -1), {re: 3, im: -1})
|
|
||||||
assert.deepStrictEqual(math.complex(false, true), {re: false, im: true})
|
|
||||||
assert.throws(() => math.complex(3, false), RangeError)
|
|
||||||
})
|
|
||||||
})
|
|
39
src/complex/__test__/type.spec.js
Normal file
39
src/complex/__test__/type.spec.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import math from '#nanomath'
|
||||||
|
|
||||||
|
const cplx = math.complex
|
||||||
|
|
||||||
|
describe('complex type operations', () => {
|
||||||
|
it('converts to number', () => {
|
||||||
|
assert.deepStrictEqual(cplx(3), {re: 3, im: 0})
|
||||||
|
assert.deepStrictEqual(cplx(NaN), {re: NaN, im: NaN})
|
||||||
|
assert.deepStrictEqual(cplx(3, -1), {re: 3, im: -1})
|
||||||
|
assert.deepStrictEqual(cplx(false, true), {re: false, im: true})
|
||||||
|
assert.throws(() => cplx(3, false), RangeError)
|
||||||
|
})
|
||||||
|
it('calculates the argument of a complex number', () => {
|
||||||
|
assert.strictEqual(math.arg(cplx(1, Math.sqrt(3))), Math.PI/3)
|
||||||
|
assert.strictEqual(math.arg(cplx(true, true)), Math.PI/4)
|
||||||
|
})
|
||||||
|
it('detects associates of a complex number', () => {
|
||||||
|
const z = cplx(3, 4)
|
||||||
|
const assoc = math.associate
|
||||||
|
assert(assoc(z, z))
|
||||||
|
assert(assoc(z, cplx(-3, -4)))
|
||||||
|
assert(assoc(z, cplx(-4, 3)))
|
||||||
|
assert(assoc(z, cplx(4, -3)))
|
||||||
|
assert(!assoc(z, math.conj(z)))
|
||||||
|
const b = cplx(true, true)
|
||||||
|
assert(assoc(b, cplx(-1, 1)))
|
||||||
|
assert(assoc(cplx(1, 1), b))
|
||||||
|
assert(assoc(cplx(-1, -1), b))
|
||||||
|
assert(assoc(cplx(1, -1), b))
|
||||||
|
assert(assoc(cplx(-1, 1), b))
|
||||||
|
assert(!assoc(b, cplx(false, true)))
|
||||||
|
assert(!assoc(cplx(0, 1), b))
|
||||||
|
})
|
||||||
|
it('computes cis of an angle', () => {
|
||||||
|
assert(math.equal(math.cis(0), 1))
|
||||||
|
assert(math.equal(math.cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2)))
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,2 +1,5 @@
|
||||||
export * as typeDefinition from './Complex.js'
|
export * as typeDefinition from './Complex.js'
|
||||||
|
export * as arithmetic from './arithmetic.js'
|
||||||
|
export * as relational from './relational.js'
|
||||||
export * as type from './type.js'
|
export * as type from './type.js'
|
||||||
|
export * as utilities from './utils.js'
|
||||||
|
|
69
src/complex/arithmetic.js
Normal file
69
src/complex/arithmetic.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import {Complex} from './Complex.js'
|
||||||
|
import {promoteBinary, promoteUnary} from './helpers.js'
|
||||||
|
import {ResolutionError} from '#core/helpers.js'
|
||||||
|
import {match} from '#core/TypePatterns.js'
|
||||||
|
import {ReturnsAs} from '#generic/helpers.js'
|
||||||
|
|
||||||
|
export const absquare = match(Complex, (math, C) => {
|
||||||
|
const compAbsq = math.absquare.resolve([C.Component])
|
||||||
|
const R = compAbsq.returns
|
||||||
|
const add = math.add.resolve([R,R])
|
||||||
|
return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const add = promoteBinary('add')
|
||||||
|
|
||||||
|
export const conj = match(Complex, (math, C) => {
|
||||||
|
const neg = math.negate.resolve(C.Component)
|
||||||
|
const compConj = math.conj.resolve(C.Component)
|
||||||
|
const cplx = math.complex.resolve([compConj.returns, neg.returns])
|
||||||
|
return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const divide = [
|
||||||
|
match([Complex, T => !T.complex], (math, [C, R]) => {
|
||||||
|
const div = math.divide.resolve([C.Component, R])
|
||||||
|
const cplx = math.complex.resolve([div.returns, div.returns])
|
||||||
|
return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r)))
|
||||||
|
}),
|
||||||
|
match([Complex, Complex], (math, [W, Z]) => {
|
||||||
|
const inv = math.invert.resolve(Z)
|
||||||
|
const mult = math.multiply.resolve([W, inv.returns])
|
||||||
|
return ReturnsAs(mult, (w, z) => mult(w, inv(z)))
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
export const invert = match(Complex, (math, C) => {
|
||||||
|
const conj = math.conj.resolve(C)
|
||||||
|
const norm = math.absquare.resolve(C)
|
||||||
|
const div = math.divide.resolve([C.Component, norm.returns])
|
||||||
|
const cplx = math.complex.resolve([div.returns, div.returns])
|
||||||
|
return ReturnsAs(cplx, z => {
|
||||||
|
const c = conj(z)
|
||||||
|
const d = norm(z)
|
||||||
|
return cplx(div(c.re, d), div(c.im, d))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// We want this to work for complex numbers, quaternions, octonions, etc
|
||||||
|
// See https://math.ucr.edu/home/baez/octonions/node5.html
|
||||||
|
export const multiply = match([Complex, Complex], (math, [W, Z]) => {
|
||||||
|
const conj = math.conj.resolve(W.Component)
|
||||||
|
if (conj.returns !== W.Component) {
|
||||||
|
throw new ResolutionError(
|
||||||
|
`conjugation on ${W.Component} returns other type (${conj.returns})`)
|
||||||
|
}
|
||||||
|
const mWZ = math.multiply.resolve([W.Component, Z.Component])
|
||||||
|
const mZW = math.multiply.resolve([Z.Component, W.Component])
|
||||||
|
const sub = math.subtract.resolve([mWZ.returns, mZW.returns])
|
||||||
|
const add = math.add.resolve([mWZ.returns, mZW.returns])
|
||||||
|
const cplx = math.complex.resolve([sub.returns, add.returns])
|
||||||
|
return ReturnsAs(cplx, (w, z) => {
|
||||||
|
const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im)))
|
||||||
|
const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im))
|
||||||
|
return cplx(real, imag)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const negate = promoteUnary('negate')
|
||||||
|
export const subtract = promoteBinary('subtract')
|
20
src/complex/helpers.js
Normal file
20
src/complex/helpers.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import {Complex} from './Complex.js'
|
||||||
|
import {match} from '#core/TypePatterns.js'
|
||||||
|
import {ReturnsAs} from '#generic/helpers.js'
|
||||||
|
|
||||||
|
export const promoteUnary = name => match(
|
||||||
|
Complex,
|
||||||
|
(math, C) => {
|
||||||
|
const compOp = math.resolve(name, C.Component)
|
||||||
|
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
|
||||||
|
return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const promoteBinary = name => match(
|
||||||
|
[Complex, Complex],
|
||||||
|
(math, [W, Z]) => {
|
||||||
|
const compOp = math.resolve(name, [W.Component, Z.Component])
|
||||||
|
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
|
||||||
|
return ReturnsAs(
|
||||||
|
cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im)))
|
||||||
|
})
|
24
src/complex/relational.js
Normal file
24
src/complex/relational.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import {Complex} from './Complex.js'
|
||||||
|
import {BooleanT} from '#boolean/BooleanT.js'
|
||||||
|
import {Returns} from '#core/Type.js'
|
||||||
|
import {match, Any, Optional} from '#core/TypePatterns.js'
|
||||||
|
|
||||||
|
export const indistinguishable = match(
|
||||||
|
[Complex, Complex, Optional([Any, Any])],
|
||||||
|
(math, [W, Z, T]) => {
|
||||||
|
let WComp = W.Component
|
||||||
|
let ZComp = Z.Component
|
||||||
|
if (T.length === 0) { // no tolerances
|
||||||
|
const same = math.indistinguishable.resolve([WComp, ZComp])
|
||||||
|
return Returns(
|
||||||
|
BooleanT, (w, z) => same(w.re, z.re) && same(w.im, z.im))
|
||||||
|
}
|
||||||
|
const [[RT, AT]] = T
|
||||||
|
const same = math.indistinguishable.resolve([WComp, ZComp, RT, AT])
|
||||||
|
return Returns(
|
||||||
|
BooleanT,
|
||||||
|
(w, z, [[rT, aT]]) => {
|
||||||
|
return same(w.re, z.re, rT, aT) && same(w.im, z.im, rT, aT)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {Complex} from './Complex.js'
|
import {Complex} from './Complex.js'
|
||||||
import {match} from "#core/helpers.js"
|
|
||||||
import {Returns} from "#core/Type.js"
|
import {Returns} from "#core/Type.js"
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {Any, match} from "#core/TypePatterns.js"
|
||||||
|
import {BooleanT} from '#boolean/BooleanT.js'
|
||||||
|
import {NumberT} from '#number/NumberT.js'
|
||||||
|
|
||||||
export const complex = [
|
export const complex = [
|
||||||
match(Any, (math, T) => {
|
match(Any, (math, T) => {
|
||||||
|
@ -25,3 +26,30 @@ export const complex = [
|
||||||
return Returns(Complex(T), (r, m) => ({re: r, im: m}))
|
return Returns(Complex(T), (r, m) => ({re: r, im: m}))
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const arg = match(
|
||||||
|
Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re)))
|
||||||
|
|
||||||
|
/* Returns true if w is z multiplied by a complex unit */
|
||||||
|
export const associate = match([Complex, Complex], (math, [W, Z]) => {
|
||||||
|
if (Z.Component.complex) {
|
||||||
|
throw new Error(
|
||||||
|
`The group of units of type ${Z} is not yet implemented`)
|
||||||
|
}
|
||||||
|
const eq = math.equal.resolve([W, Z])
|
||||||
|
const neg = math.negate.resolve(Z)
|
||||||
|
const eqN = math.equal.resolve([W, neg.returns])
|
||||||
|
const mult = math.multiply.resolve([Z, Z])
|
||||||
|
const eqM = math.equal.resolve([W, mult.returns])
|
||||||
|
const negM = math.negate.resolve(mult.returns)
|
||||||
|
const eqNM = math.equal.resolve([W, negM.returns])
|
||||||
|
const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component))
|
||||||
|
return Returns(BooleanT, (w, z) => {
|
||||||
|
if (eq(w, z) || eqN(w, neg(z))) return true
|
||||||
|
const iz = mult(iZ, z)
|
||||||
|
return eqM(w, iz) || eqNM(w, negM(iz))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const cis = match(NumberT, Returns(Complex(NumberT), t => ({
|
||||||
|
re: Math.cos(t), im: Math.sin(t)})))
|
||||||
|
|
9
src/complex/utils.js
Normal file
9
src/complex/utils.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import {Complex} from './Complex.js'
|
||||||
|
import {match} from '#core/TypePatterns.js'
|
||||||
|
import {ReturnsAs} from '#generic/helpers.js'
|
||||||
|
|
||||||
|
export const isReal = match(Complex, (math, C) => {
|
||||||
|
const eq = math.equal.resolve([C.Component, C.Component])
|
||||||
|
const add = math.add.resolve([C.Component, C.Component])
|
||||||
|
return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im)))
|
||||||
|
})
|
13
src/core/Implementations.js
Normal file
13
src/core/Implementations.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export class Implementations {
|
||||||
|
constructor(impOrImps) {
|
||||||
|
if (Array.isArray(impOrImps)) {
|
||||||
|
this.matchers = impOrImps
|
||||||
|
} else this.matchers = [impOrImps]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ImplementationsGenerator {
|
||||||
|
constructor(f) {
|
||||||
|
this.generate = f
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
import ArrayKeyedMap from 'array-keyed-map'
|
import ArrayKeyedMap from 'array-keyed-map'
|
||||||
import {match} from './helpers.js'
|
|
||||||
import {Passthru} from './TypePatterns.js'
|
|
||||||
|
|
||||||
// Generic types are callable, so we have no choice but to extend Function
|
// Generic types are callable, so we have no choice but to extend Function
|
||||||
export class Type extends Function {
|
export class Type extends Function {
|
||||||
|
@ -12,9 +10,8 @@ export class Type extends Function {
|
||||||
// let the proxy out of this function, never `this`.
|
// let the proxy out of this function, never `this`.
|
||||||
const rewired = new Proxy(this, {
|
const rewired = new Proxy(this, {
|
||||||
apply: (target, thisForCall, args) => {
|
apply: (target, thisForCall, args) => {
|
||||||
const callThrough = thisForCall ?? target
|
if (target.specialize) return target.specialize(...args)
|
||||||
if (callThrough.specialize) return callThrough.specialize(...args)
|
throw new TypeError(`Type ${target} is not generic`)
|
||||||
throw new TypeError(`Type ${callThrough} is not generic`)
|
|
||||||
},
|
},
|
||||||
get: (target, prop, receiver) => {
|
get: (target, prop, receiver) => {
|
||||||
if (prop === 'isAproxy') return true
|
if (prop === 'isAproxy') return true
|
||||||
|
@ -87,13 +84,3 @@ export const whichType = typs => Returns(TypeOfTypes, item => {
|
||||||
}
|
}
|
||||||
throw new TypeError(errorMsg)
|
throw new TypeError(errorMsg)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const typeOf = match(Passthru, math => whichType(math.types))
|
|
||||||
|
|
||||||
// bootstrapping order matters, but order of exports in a module isn't
|
|
||||||
// simply the order that the items are listed in the module. So we make
|
|
||||||
// an explicitly ordered export of implementations for this sake:
|
|
||||||
|
|
||||||
export const bootstrapTypes = {
|
|
||||||
Type, Undefined, TypeOfTypes, typeOf
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import ArrayKeyedMap from 'array-keyed-map'
|
import ArrayKeyedMap from 'array-keyed-map'
|
||||||
|
|
||||||
|
import {ResolutionError, isPlainFunction, isPlainObject} from './helpers.js'
|
||||||
|
import {Implementations, ImplementationsGenerator} from './Implementations.js'
|
||||||
|
import {bootstrapTypes} from './type.js'
|
||||||
|
import {Returns, whichType, Type} from './Type.js'
|
||||||
import {
|
import {
|
||||||
Implementations, ImplementationsGenerator, Matcher, ResolutionError,
|
matched, needsCollection, Passthru, Matcher, match
|
||||||
isPlainFunction, isPlainObject, match, types
|
} from './TypePatterns.js'
|
||||||
} from './helpers.js'
|
|
||||||
import {bootstrapTypes, Returns, whichType, Type} from './Type.js'
|
const underResolution = Symbol('underResolution')
|
||||||
import {matched, needsCollection, Passthru} from './TypePatterns.js'
|
|
||||||
|
|
||||||
export class TypeDispatcher {
|
export class TypeDispatcher {
|
||||||
constructor(...specs) {
|
constructor(...specs) {
|
||||||
|
@ -14,7 +17,6 @@ export class TypeDispatcher {
|
||||||
this._behaviors = {} // maps key to a map from type vectors to results
|
this._behaviors = {} // maps key to a map from type vectors to results
|
||||||
this._fallbacks = {} // maps key to a catchall result
|
this._fallbacks = {} // maps key to a catchall result
|
||||||
// bootstrap the instance
|
// bootstrap the instance
|
||||||
this.merge({types})
|
|
||||||
this.merge(bootstrapTypes)
|
this.merge(bootstrapTypes)
|
||||||
for (const spec of specs) this.merge(spec)
|
for (const spec of specs) this.merge(spec)
|
||||||
}
|
}
|
||||||
|
@ -261,6 +263,7 @@ export class TypeDispatcher {
|
||||||
if (!(key in this)) {
|
if (!(key in this)) {
|
||||||
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]
|
||||||
|
|
||||||
const generatingDeps = this.resolve._genDepsOf?.length
|
const generatingDeps = this.resolve._genDepsOf?.length
|
||||||
if (generatingDeps) this._addToDeps(key, types)
|
if (generatingDeps) this._addToDeps(key, types)
|
||||||
|
@ -269,6 +272,10 @@ export class TypeDispatcher {
|
||||||
// Return the cached resolution if it's there
|
// Return the cached resolution if it's there
|
||||||
if (behave.has(types)) {
|
if (behave.has(types)) {
|
||||||
const result = behave.get(types)
|
const result = behave.get(types)
|
||||||
|
if (result === underResolution) {
|
||||||
|
throw new ResolutionError(
|
||||||
|
`recursive resolution of ${key} on ${types}`)
|
||||||
|
}
|
||||||
if (generatingDeps
|
if (generatingDeps
|
||||||
&& typeof result === 'object'
|
&& typeof result === 'object'
|
||||||
&& !(result instanceof Type)
|
&& !(result instanceof Type)
|
||||||
|
@ -328,13 +335,14 @@ export class TypeDispatcher {
|
||||||
let theBehavior = () => undefined
|
let theBehavior = () => undefined
|
||||||
let finalBehavior
|
let finalBehavior
|
||||||
this.resolve._genDepsOf.push([key, types])
|
this.resolve._genDepsOf.push([key, types])
|
||||||
|
behave.set(types, underResolution)
|
||||||
try { // Used to make sure not to return without popping _genDepsOf
|
try { // Used to make sure not to return without popping _genDepsOf
|
||||||
if (!('returns' in item)) {
|
if (!('returns' in item)) {
|
||||||
// looks like a factory
|
// looks like a factory
|
||||||
try {
|
try {
|
||||||
theBehavior = item(
|
theBehavior = item(
|
||||||
DependencyRecorder(this, '', this, []),
|
DependencyRecorder(this, '', this, []),
|
||||||
matched(template))
|
matched(template, this))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.message = `Error in factory for ${key} on ${types} `
|
e.message = `Error in factory for ${key} on ${types} `
|
||||||
+ `(match data ${template}): ${e.message}`
|
+ `(match data ${template}): ${e.message}`
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {Type, Undefined} from './Type.js'
|
import {Type, Undefined} from './Type.js'
|
||||||
|
import {isPlainFunction} from './helpers.js'
|
||||||
|
|
||||||
export class TypePattern {
|
export class TypePattern {
|
||||||
match(typeSequence, options={}) {
|
match(typeSequence, options={}) {
|
||||||
|
@ -64,12 +65,35 @@ class SequencePattern extends TypePattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PredicatePattern extends TypePattern {
|
||||||
|
constructor(predicate) {
|
||||||
|
super()
|
||||||
|
this.predicate = predicate
|
||||||
|
}
|
||||||
|
match(typeSequence, options={}) {
|
||||||
|
const position = options.position ?? 0
|
||||||
|
if (position >= typeSequence.length) return [-1, undefined]
|
||||||
|
const actual = typeSequence[position]
|
||||||
|
if (this.predicate(actual)) return [position + 1, actual]
|
||||||
|
return [-1, Undefined]
|
||||||
|
}
|
||||||
|
sampleTypes() {
|
||||||
|
throw new Error('sampleTypes() not yet implemented for PredicatePattern')
|
||||||
|
}
|
||||||
|
equal(other) {
|
||||||
|
return super.equal(other) && this.predicate === other.predicate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const pattern = patternOrSpec => {
|
export const pattern = patternOrSpec => {
|
||||||
if (patternOrSpec instanceof TypePattern) return patternOrSpec
|
if (patternOrSpec instanceof TypePattern) return patternOrSpec
|
||||||
if (patternOrSpec instanceof Type) {
|
if (patternOrSpec instanceof Type) {
|
||||||
return new MatchTypePattern(patternOrSpec)
|
return new MatchTypePattern(patternOrSpec)
|
||||||
}
|
}
|
||||||
if (Array.isArray(patternOrSpec)) return new SequencePattern(patternOrSpec)
|
if (Array.isArray(patternOrSpec)) return new SequencePattern(patternOrSpec)
|
||||||
|
if (isPlainFunction(patternOrSpec)) {
|
||||||
|
return new PredicatePattern(patternOrSpec)
|
||||||
|
}
|
||||||
throw new TypeError(`Can't interpret '${patternOrSpec}' as a type pattern`)
|
throw new TypeError(`Can't interpret '${patternOrSpec}' as a type pattern`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,9 +174,16 @@ class PassthruPattern extends TypePattern {
|
||||||
export const Passthru = new PassthruPattern()
|
export const Passthru = new PassthruPattern()
|
||||||
|
|
||||||
// returns the template just of matched types, dropping any actual types
|
// returns the template just of matched types, dropping any actual types
|
||||||
export const matched = (template) => {
|
export const matched = (template, math) => {
|
||||||
if (Array.isArray(template)) return template.map(matched)
|
if (Array.isArray(template)) {
|
||||||
return template.matched ?? template
|
return template.map(pattern => matched(pattern, math))
|
||||||
|
}
|
||||||
|
if (template.matched) {
|
||||||
|
let convert = template.convertor
|
||||||
|
if (!convert.returns) convert = convert(math, template.actual)
|
||||||
|
return convert.returns || template.matched
|
||||||
|
}
|
||||||
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks if the template is just pass-through or needs collection
|
// checks if the template is just pass-through or needs collection
|
||||||
|
@ -163,3 +194,12 @@ export const needsCollection = (template) => {
|
||||||
}
|
}
|
||||||
return 'actual' in template
|
return 'actual' in template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Matcher {
|
||||||
|
constructor(spec, facOrBehave) {
|
||||||
|
this.pattern = pattern(spec)
|
||||||
|
this.does = facOrBehave
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const match = (spec, facOrBehave) => new Matcher(spec, facOrBehave)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import * as booleans from '#boolean/all.js'
|
||||||
import * as generics from '#generic/all.js'
|
import * as generics from '#generic/all.js'
|
||||||
import * as numbers from '#number/all.js'
|
import * as numbers from '#number/all.js'
|
||||||
import {NumberT} from '#number/NumberT.js'
|
import {NumberT} from '#number/NumberT.js'
|
||||||
import {match, ResolutionError} from "#core/helpers.js"
|
import {ResolutionError} from "#core/helpers.js"
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {match, Any} from "#core/TypePatterns.js"
|
||||||
import {Returns, NotAType} from "#core/Type.js"
|
import {Returns, NotAType} from "#core/Type.js"
|
||||||
import {plain} from "#number/helpers.js"
|
import {plain} from "#number/helpers.js"
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import {
|
import {isPlainObject, isPlainFunction} from '../helpers.js'
|
||||||
Matcher, match, isPlainObject, isPlainFunction, Implementations
|
import {Implementations} from '../Implementations.js'
|
||||||
} from '../helpers.js'
|
|
||||||
import {Type, Undefined, TypeOfTypes} from '../Type.js'
|
import {Type, Undefined, TypeOfTypes} from '../Type.js'
|
||||||
import {TypePattern} from '../TypePatterns.js'
|
import {match, Matcher, TypePattern} from '../TypePatterns.js'
|
||||||
|
|
||||||
describe('Core helpers', () => {
|
describe('Core helpers', () => {
|
||||||
it('defines what Matchers are', () => {
|
it('defines what Matchers are', () => {
|
||||||
|
|
|
@ -1,41 +1,3 @@
|
||||||
import {pattern, Passthru} from './TypePatterns.js'
|
|
||||||
|
|
||||||
export class Matcher {
|
|
||||||
constructor(spec, facOrBehave) {
|
|
||||||
this.pattern = pattern(spec)
|
|
||||||
this.does = facOrBehave
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Implementations {
|
|
||||||
constructor(impOrImps) {
|
|
||||||
if (Array.isArray(impOrImps)) {
|
|
||||||
this.matchers = impOrImps
|
|
||||||
} else this.matchers = [impOrImps]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const match = (spec, facOrBehave) => new Matcher(spec, facOrBehave)
|
|
||||||
|
|
||||||
export class ImplementationsGenerator {
|
|
||||||
constructor(f) {
|
|
||||||
this.generate = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the archetypal example of needing an ImplementationsGenerator:
|
|
||||||
// each TypeDispatcher must have a types property, which will be a
|
|
||||||
// plain object of types. This must be a different object for each
|
|
||||||
// TypeDispatcher, but the same object regardless of the types vector
|
|
||||||
// passed to resolve. So an ordinary factory won't work, because it
|
|
||||||
// would make a new plain object for each different types vector that
|
|
||||||
// the property `types` was resolved with. And just a plain object
|
|
||||||
// wouldn't work, because then every TypeDispatcher would have the same
|
|
||||||
// collection of types (and modifying the types in one would affect them
|
|
||||||
// all). Hence we do:
|
|
||||||
|
|
||||||
export const types = new ImplementationsGenerator(() => match(Passthru, {}))
|
|
||||||
|
|
||||||
export class ResolutionError extends TypeError {
|
export class ResolutionError extends TypeError {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args)
|
super(...args)
|
||||||
|
|
26
src/core/type.js
Normal file
26
src/core/type.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import {ImplementationsGenerator} from './Implementations.js'
|
||||||
|
import {Type, TypeOfTypes, Undefined, whichType} from './Type.js'
|
||||||
|
import {match, Passthru} from './TypePatterns.js'
|
||||||
|
|
||||||
|
export const typeOf = match(Passthru, math => whichType(math.types))
|
||||||
|
|
||||||
|
// And now the types object itself: This property provides the archetypal
|
||||||
|
// example of needing an ImplementationsGenerator. Each TypeDispatcher must
|
||||||
|
// have a types property, which will be a plain object of types. This object
|
||||||
|
// must be different for each TypeDispatcher, but within a TypeDispatcher,
|
||||||
|
// it must be the same object regardless of the types vector passed to resolve.
|
||||||
|
// So an ordinary factory won't work, because it would make a new plain object
|
||||||
|
// for each different types vector that the property `types` was resolved with.
|
||||||
|
// And just a plain object wouldn't work, because then every TypeDispatcher
|
||||||
|
// would have the same collection of types (and modifying the types in one
|
||||||
|
// would affect them all). Hence we do:
|
||||||
|
|
||||||
|
export const types = new ImplementationsGenerator(() => match(Passthru, {}))
|
||||||
|
|
||||||
|
// bootstrapping order matters, but order of exports in a module isn't
|
||||||
|
// simply the order that the items are listed in the module. So we make
|
||||||
|
// an explicitly ordered export of implementations for this sake:
|
||||||
|
|
||||||
|
export const bootstrapTypes = {
|
||||||
|
types, Type, Undefined, TypeOfTypes, typeOf
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import {match} from '#core/helpers.js'
|
|
||||||
import {TypeOfTypes, Undefined} from '#core/Type.js'
|
import {TypeOfTypes, Undefined} from '#core/Type.js'
|
||||||
|
import {match} from '#core/TypePatterns.js'
|
||||||
import {boolnum} from '#number/helpers.js'
|
import {boolnum} from '#number/helpers.js'
|
||||||
|
|
||||||
export const indistinguishable = [
|
export const indistinguishable = [
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {match} from '#core/helpers.js'
|
|
||||||
import {NotAType, Returns, TypeOfTypes} from '#core/Type.js'
|
import {NotAType, Returns, TypeOfTypes} from '#core/Type.js'
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {match,Any} from "#core/TypePatterns.js"
|
||||||
import {boolnum} from "#number/helpers.js"
|
import {boolnum} from "#number/helpers.js"
|
||||||
|
|
||||||
export const zero = [
|
export const zero = [
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import math from '#nanomath'
|
import math from '#nanomath'
|
||||||
|
import {NumberT} from '#number/NumberT.js'
|
||||||
|
|
||||||
describe('generic utility functions', () => {
|
describe('generic utility functions', () => {
|
||||||
it('tests whether an element is zero', () => {
|
it('tests whether an element is zero', () => {
|
||||||
|
@ -9,5 +10,16 @@ describe('generic utility functions', () => {
|
||||||
assert(isZero(false))
|
assert(isZero(false))
|
||||||
assert(!isZero(true))
|
assert(!isZero(true))
|
||||||
assert(isZero(undefined))
|
assert(isZero(undefined))
|
||||||
|
assert(isZero(math.types.Complex(NumberT).zero))
|
||||||
|
assert(isZero(math.complex(-2e-16, 4e-17)))
|
||||||
|
assert(!isZero(math.complex(true)))
|
||||||
|
})
|
||||||
|
it('tests whether an element is real', () => {
|
||||||
|
const {isReal} = math
|
||||||
|
assert(isReal(Infinity))
|
||||||
|
assert(isReal(false))
|
||||||
|
assert(isReal(math.types.Complex(NumberT).one))
|
||||||
|
assert(isReal(math.complex(-3.25, 4e-16)))
|
||||||
|
assert(!isReal(math.complex(3, 4)))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {match} from '#core/helpers.js'
|
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Any} from '#core/TypePatterns.js'
|
import {match, Any} from '#core/TypePatterns.js'
|
||||||
|
|
||||||
|
export const conj = match(Any, (_math, T) => Returns(T, a => a))
|
||||||
export const square = match(Any, (math, T) => {
|
export const square = match(Any, (math, T) => {
|
||||||
const mult = math.multiply.resolve([T, T])
|
const mult = math.multiply.resolve([T, T])
|
||||||
return Returns(mult.returns, a => mult(a, a))
|
return Returns(mult.returns, a => mult(a, a))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {ImplementationsGenerator, match} from '#core/helpers.js'
|
import {ImplementationsGenerator} from '#core/Implementations.js'
|
||||||
import {Passthru} from '#core/TypePatterns.js'
|
import {match, Passthru} from '#core/TypePatterns.js'
|
||||||
|
|
||||||
export const config = new ImplementationsGenerator(
|
export const config = new ImplementationsGenerator(
|
||||||
() => match(Passthru, {relTol: 1e-12, absTol: 1e-15}))
|
() => match(Passthru, {relTol: 1e-12, absTol: 1e-15}))
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {ReturnsAs} from './helpers.js'
|
import {ReturnsAs} from './helpers.js'
|
||||||
import {match} from '#core/helpers.js'
|
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Any, Passthru, matched} from '#core/TypePatterns.js'
|
import {Any, Passthru, match, matched} from '#core/TypePatterns.js'
|
||||||
import {boolnum} from '#number/helpers.js'
|
import {boolnum} from '#number/helpers.js'
|
||||||
|
|
||||||
export const equal = match([Any, Any], (math, [T, U]) => {
|
export const equal = match([Any, Any], (math, [T, U]) => {
|
||||||
|
@ -18,7 +17,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
|
||||||
return boolnum(() => false)(math)
|
return boolnum(() => false)(math)
|
||||||
}
|
}
|
||||||
// Get the type of the first argument to the matching checker:
|
// Get the type of the first argument to the matching checker:
|
||||||
const ByType = matched(exactChecker.template).flat()[0]
|
const ByType = matched(exactChecker.template, math).flat()[0]
|
||||||
// Now see if there are tolerances for that type:
|
// Now see if there are tolerances for that type:
|
||||||
const typeConfig = math.resolve('config', [ByType])
|
const typeConfig = math.resolve('config', [ByType])
|
||||||
if ('relTol' in typeConfig) {
|
if ('relTol' in typeConfig) {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import {ReturnsAs} from './helpers.js'
|
import {ReturnsAs} from './helpers.js'
|
||||||
import {ResolutionError, match} from '#core/helpers.js'
|
import {ResolutionError} from '#core/helpers.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Passthru, match} from '#core/TypePatterns.js'
|
||||||
import {Passthru} from "#core/TypePatterns.js"
|
import {boolnum} from '#number/helpers.js'
|
||||||
|
|
||||||
|
// Most types are real. Have to make sure to redefine on all non-real types
|
||||||
|
export const isReal = match(Passthru, boolnum(() => true))
|
||||||
export const isZero = match(Passthru, (math, [T]) => {
|
export const isZero = match(Passthru, (math, [T]) => {
|
||||||
if (!T) { // called with no arguments
|
if (!T) { // called with no arguments
|
||||||
throw new ResolutionError('isZero() requires one argument')
|
throw new ResolutionError('isZero() requires one argument')
|
||||||
|
|
|
@ -5,6 +5,16 @@ import * as numbers from './number/all.js'
|
||||||
import * as complex from './complex/all.js'
|
import * as complex from './complex/all.js'
|
||||||
import {TypeDispatcher} from '#core/TypeDispatcher.js'
|
import {TypeDispatcher} from '#core/TypeDispatcher.js'
|
||||||
|
|
||||||
const math = new TypeDispatcher(booleans, coretypes, generics, numbers, complex)
|
// At the moment, since we are not sorting patterns in any way,
|
||||||
|
// order matters in the construction. Patterns that come later in
|
||||||
|
// the following list will be tried earlier. (The rationale for that
|
||||||
|
// ordering is that any time you merge something, it should supersede
|
||||||
|
// whatever has been merged before.)
|
||||||
|
// Hence, in building the math instance, we put generics first because
|
||||||
|
// they should only kick in when there are not specific implementations,
|
||||||
|
// and complex next becausewe want its conversion (which converts _any_
|
||||||
|
// non-complex type to complex, potentially making a poor overload choice)
|
||||||
|
// to be tried last.
|
||||||
|
const math = new TypeDispatcher(generics, complex, booleans, coretypes, numbers)
|
||||||
|
|
||||||
export default math
|
export default math
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import {Type} from '#core/Type.js'
|
import {Type} from '#core/Type.js'
|
||||||
import {match} from '#core/helpers.js'
|
import {match} from '#core/TypePatterns.js'
|
||||||
import {BooleanT} from '#boolean/BooleanT.js'
|
import {BooleanT} from '#boolean/BooleanT.js'
|
||||||
|
|
||||||
export const NumberT = new Type(n => typeof n === 'number', {
|
export const NumberT = new Type(n => typeof n === 'number', {
|
||||||
from: match(BooleanT, math => math.number.resolve([BooleanT])),
|
from: match(BooleanT, math => math.number.resolve([BooleanT])),
|
||||||
|
typeName: 'NumberT', // since used before ever put in a TypeDispatcher
|
||||||
one: 1,
|
one: 1,
|
||||||
zero: 0,
|
zero: 0,
|
||||||
nan: NaN
|
nan: NaN
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {NumberT} from './NumberT.js'
|
import {NumberT} from './NumberT.js'
|
||||||
|
|
||||||
import {match} from '#core/helpers.js'
|
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
|
import {match} from '#core/TypePatterns.js'
|
||||||
|
|
||||||
export const plain = f => match(
|
export const plain = f => match(
|
||||||
Array(f.length).fill(NumberT), Returns(NumberT, f))
|
Array(f.length).fill(NumberT), Returns(NumberT, f))
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {match} from '#core/helpers.js'
|
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Optional} from '#core/TypePatterns.js'
|
import {match, Optional} from '#core/TypePatterns.js'
|
||||||
import {boolnum} from './helpers.js'
|
import {boolnum} from './helpers.js'
|
||||||
import {NumberT} from './NumberT.js'
|
import {NumberT} from './NumberT.js'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {plain} from './helpers.js'
|
import {plain} from './helpers.js'
|
||||||
import {BooleanT} from '#boolean/BooleanT.js'
|
import {BooleanT} from '#boolean/BooleanT.js'
|
||||||
import {match} from '#core/helpers.js'
|
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
|
import {match} from '#core/TypePatterns.js'
|
||||||
import {NumberT} from '#number/NumberT.js'
|
import {NumberT} from '#number/NumberT.js'
|
||||||
|
|
||||||
const num = f => Returns(NumberT, f)
|
const num = f => Returns(NumberT, f)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {plain, boolnum} from './helpers.js'
|
||||||
import {NumberT} from './NumberT.js'
|
import {NumberT} from './NumberT.js'
|
||||||
|
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {match} from '#core/helpers.js'
|
import {match} from '#core/TypePatterns.js'
|
||||||
|
|
||||||
export const clone = plain(a => a)
|
export const clone = plain(a => a)
|
||||||
export const isnan = match(NumberT, boolnum(isNaN))
|
export const isnan = match(NumberT, boolnum(isNaN))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue