feat: add nan to types, add hasnan and sign functions
All checks were successful
/ test (pull_request) Successful in 19s
All checks were successful
/ test (pull_request) Successful in 19s
Also makes certain to clean up the dependencies that are being generated even in case of throwing an error in the factory for a method.
This commit is contained in:
parent
686cd93927
commit
c1791ddc20
8 changed files with 114 additions and 42 deletions
|
@ -4,6 +4,7 @@ export class Type {
|
||||||
this.from = options.from ?? {patterns: []} // mock empty Implementations
|
this.from = options.from ?? {patterns: []} // mock empty Implementations
|
||||||
if ('zero' in options) this.zero = options.zero
|
if ('zero' in options) this.zero = options.zero
|
||||||
if ('one' in options) this.one = options.one
|
if ('one' in options) this.one = options.one
|
||||||
|
if ('nan' in options) this.nan = options.nan
|
||||||
}
|
}
|
||||||
toString() {
|
toString() {
|
||||||
return this.name || `[Type ${this.test}]`
|
return this.name || `[Type ${this.test}]`
|
||||||
|
@ -12,7 +13,7 @@ export class Type {
|
||||||
|
|
||||||
export const Undefined = new Type(
|
export const Undefined = new Type(
|
||||||
t => typeof t === 'undefined',
|
t => typeof t === 'undefined',
|
||||||
{zero: undefined, one: 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(t => true) // Danger, do not merge!
|
export const NotAType = new Type(t => true) // Danger, do not merge!
|
||||||
NotAType._doNotMerge = true
|
NotAType._doNotMerge = true
|
||||||
|
|
|
@ -309,39 +309,41 @@ export class TypeDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
let theBehavior = () => undefined
|
let theBehavior = () => undefined
|
||||||
this.resolve._genDepsOf.push([key, types]) // Important: make sure
|
let finalBehavior
|
||||||
// not to return without popping _genDepsOf
|
this.resolve._genDepsOf.push([key, types])
|
||||||
if (!('returns' in item)) {
|
try { // Used to make sure not to return without popping _genDepsOf
|
||||||
// looks like a factory
|
if (!('returns' in item)) {
|
||||||
try {
|
// looks like a factory
|
||||||
theBehavior = item(
|
try {
|
||||||
DependencyRecorder(this, '', this, []),
|
theBehavior = item(
|
||||||
matched(template))
|
DependencyRecorder(this, '', this, []),
|
||||||
} catch (e) {
|
matched(template))
|
||||||
e.message = `Error in factory for ${key} on ${types} `
|
} catch (e) {
|
||||||
+ `(match data ${template}): ${e.message}`
|
e.message = `Error in factory for ${key} on ${types} `
|
||||||
throw e
|
+ `(match data ${template}): ${e.message}`
|
||||||
}
|
throw e
|
||||||
} else theBehavior = item
|
}
|
||||||
|
} else theBehavior = item
|
||||||
|
|
||||||
let finalBehavior = theBehavior
|
finalBehavior = theBehavior
|
||||||
if (typeof theBehavior === 'function') {
|
if (typeof theBehavior === 'function') {
|
||||||
const returning = theBehavior.returns
|
const returning = theBehavior.returns
|
||||||
if (!returning) {
|
if (!returning) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
`No return type specified for ${key} on ${types}`)
|
`No return type specified for ${key} on ${types}`)
|
||||||
}
|
}
|
||||||
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
|
||||||
// argument transformer may generate more dependencies.
|
// argument transformer may generate more dependencies.
|
||||||
const morph = this._generateCollectFunction(template)
|
const morph = this._generateCollectFunction(template)
|
||||||
finalBehavior =
|
finalBehavior =
|
||||||
Returns(returning, (...args) => theBehavior(...morph(args)))
|
Returns(returning, (...args) => theBehavior(...morph(args)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
this.resolve._genDepsOf.pop() // OK, now it's safe to return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resolve._genDepsOf.pop() // OK, now it's safe to return
|
|
||||||
behave.set(types, finalBehavior)
|
behave.set(types, finalBehavior)
|
||||||
finalBehavior.template = template
|
finalBehavior.template = template
|
||||||
return finalBehavior
|
return finalBehavior
|
||||||
|
|
|
@ -11,4 +11,14 @@ describe('core type utility functions', () => {
|
||||||
assert.strictEqual(math.one(undefined), undefined)
|
assert.strictEqual(math.one(undefined), undefined)
|
||||||
assert.strictEqual(math.zero(true), false)
|
assert.strictEqual(math.zero(true), false)
|
||||||
})
|
})
|
||||||
|
it('identifies whether types have a NotANumber element', () => {
|
||||||
|
assert(math.hasnan(math.types.NumberT))
|
||||||
|
assert(math.hasnan(73.2))
|
||||||
|
assert(math.hasnan(math.types.Undefined))
|
||||||
|
assert(math.hasnan(undefined))
|
||||||
|
assert(!math.hasnan(math.types.BooleanT))
|
||||||
|
assert(!math.hasnan(true))
|
||||||
|
assert(isNaN(math.nan(math.types.NumberT)))
|
||||||
|
assert(isNaN(math.nan(-470.1)))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {onType} from '#core/helpers.js'
|
import {onType} 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 {Any} from "#core/TypePatterns.js"
|
||||||
|
import {boolnum} from "#number/helpers.js"
|
||||||
|
|
||||||
export const zero = onType(
|
export const zero = onType(
|
||||||
Any, (math, T) => {
|
Any, (math, T) => {
|
||||||
|
@ -24,3 +25,23 @@ export const one = onType(
|
||||||
`type '${t}' has no unit element designated as "one"`)
|
`type '${t}' has no unit element designated as "one"`)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const hasnan = onType(
|
||||||
|
Any, (math, T) => {
|
||||||
|
const answer = math.hasnan(T)
|
||||||
|
return Returns(math.typeOf(answer), () => answer)
|
||||||
|
},
|
||||||
|
TypeOfTypes, boolnum(t => 'nan' in t)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const nan = onType(
|
||||||
|
Any, (math, T) => {
|
||||||
|
const notanum = math.nan(T)
|
||||||
|
return Returns(T, () => notanum)
|
||||||
|
},
|
||||||
|
TypeOfTypes, Returns(NotAType, t => {
|
||||||
|
if ('nan' in t) return t.nan
|
||||||
|
throw new RangeError(
|
||||||
|
`type '${t}' has no "not a number" element`)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
|
@ -45,9 +45,18 @@ describe('generic relational functions', () => {
|
||||||
assert.strictEqual(compare(2.2, true), 1)
|
assert.strictEqual(compare(2.2, true), 1)
|
||||||
assert.strictEqual(compare(-Infinity, 7), -1)
|
assert.strictEqual(compare(-Infinity, 7), -1)
|
||||||
assert(isNaN(compare(NaN, 0)))
|
assert(isNaN(compare(NaN, 0)))
|
||||||
assert(isNaN(compare(false, NaN)))
|
assert.throws(() => compare(false, NaN), TypeError)
|
||||||
assert(isNaN(compare(NaN, NaN)))
|
assert(isNaN(compare(NaN, NaN)))
|
||||||
assert.strictEqual(compare(true, false), 1)
|
assert.throws(() => compare(true, false), TypeError)
|
||||||
assert.throws(() => compare(undefined, -1), ResolutionError)
|
assert.throws(() => compare(undefined, -1), ResolutionError)
|
||||||
})
|
})
|
||||||
|
it('determines the sign of numeric values', () => {
|
||||||
|
const {sign} = math
|
||||||
|
assert.strictEqual(sign(-8e-16), 0)
|
||||||
|
assert.strictEqual(sign(Infinity), 1)
|
||||||
|
assert.strictEqual(sign(-8e-14), -1)
|
||||||
|
assert(isNaN(sign(NaN)))
|
||||||
|
assert.throws(() => sign(false), TypeError)
|
||||||
|
assert.throws(() => sign(undefined), ResolutionError)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {onType} from '#core/helpers.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Any, matched} from '#core/TypePatterns.js'
|
import {Any, matched} from '#core/TypePatterns.js'
|
||||||
import {boolnum} from '#number/helpers.js'
|
import {boolnum} from '#number/helpers.js'
|
||||||
import {NumberT} from '#number/NumberT.js'
|
|
||||||
|
|
||||||
export const equal = onType([Any, Any], (math, [T, U]) => {
|
export const equal = onType([Any, Any], (math, [T, U]) => {
|
||||||
// Finding the correct signature of `indistinguishable` to use for
|
// Finding the correct signature of `indistinguishable` to use for
|
||||||
|
@ -42,11 +41,37 @@ export const equal = onType([Any, Any], (math, [T, U]) => {
|
||||||
export const compare = onType([Any, Any], (math, [T, U]) => {
|
export const compare = onType([Any, Any], (math, [T, U]) => {
|
||||||
const eq = math.equal.resolve([T, U])
|
const eq = math.equal.resolve([T, U])
|
||||||
const gt = math.exceeds.resolve([T, U])
|
const gt = math.exceeds.resolve([T, U])
|
||||||
const isTnan = math.isnan.resolve([T])
|
const zero = math.zero(T) // asymmetry here is unfortunate, but we have
|
||||||
const isUnan = math.isnan.resolve([U])
|
// to pick some argument's zero to use, so the first argument seemed
|
||||||
return Returns(NumberT, (t, u) => {
|
// the most reasonable.
|
||||||
if (isTnan(t) || isUnan(u)) return NaN
|
const one = math.one(T)
|
||||||
if (eq(t,u)) return 0
|
const negOne = math.negate(one)
|
||||||
return gt(t, u) ? 1 : -1
|
if (math.typeOf(negOne) !== T) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Cannot 'compare()' type '${T}' that has no negative one.`)
|
||||||
|
}
|
||||||
|
const hasnanT = math.hasnan(T)
|
||||||
|
const hasnanU = math.hasnan(U)
|
||||||
|
if (!hasnanT && !hasnanU) {
|
||||||
|
return Returns(T, (t, u) => eq(t, u) ? zero : gt(t, u) ? one : negOne)
|
||||||
|
}
|
||||||
|
if (hasnanU && !hasnanT) {
|
||||||
|
throw new TypeError(
|
||||||
|
`can't compare type ${T} without NaN and type ${U} with NaN`)
|
||||||
|
}
|
||||||
|
const isTnan = hasnanT && math.isnan.resolve([T])
|
||||||
|
const isUnan = hasnanU && math.isnan.resolve([U])
|
||||||
|
const nanT = hasnanT && math.nan(T)
|
||||||
|
return Returns(T, (t, u) => {
|
||||||
|
if (hasnanT && isTnan(t)) return nanT
|
||||||
|
if (hasnanU && isUnan(u)) return nanT // not a typo, stay in T
|
||||||
|
if (eq(t, u)) return zero
|
||||||
|
return gt(t, u) ? one : negOne
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const sign = onType(Any, (math, T) => {
|
||||||
|
const zero = math.zero(T)
|
||||||
|
const comp = math.compare.resolve([T, T])
|
||||||
|
return ReturnsAs(comp, t => comp(t, zero))
|
||||||
|
})
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import {ReturnsAs} from './helpers.js'
|
import {ReturnsAs} from './helpers.js'
|
||||||
import {onType} from '#core/helpers.js'
|
import {onType, ResolutionError} from '#core/helpers.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {Any} from "#core/TypePatterns.js"
|
||||||
|
|
||||||
export const isZero = (math, [T]) => {
|
export const isZero = (math, [T]) => {
|
||||||
|
if (!T) { // called with no arguments
|
||||||
|
throw new ResolutionError('isZero() requires one argument')
|
||||||
|
}
|
||||||
const z = math.zero(T)
|
const z = math.zero(T)
|
||||||
const eq = math.equal.resolve([T, T])
|
const eq = math.equal.resolve([T, T])
|
||||||
return ReturnsAs(eq, x => eq(z, x))
|
return ReturnsAs(eq, x => eq(z, x))
|
||||||
|
|
|
@ -5,5 +5,6 @@ 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: onType(BooleanT, math => math.number.resolve([BooleanT])),
|
from: onType(BooleanT, math => math.number.resolve([BooleanT])),
|
||||||
one: 1,
|
one: 1,
|
||||||
zero: 0
|
zero: 0,
|
||||||
|
nan: NaN
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue