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
|
||||
if ('zero' in options) this.zero = options.zero
|
||||
if ('one' in options) this.one = options.one
|
||||
if ('nan' in options) this.nan = options.nan
|
||||
}
|
||||
toString() {
|
||||
return this.name || `[Type ${this.test}]`
|
||||
|
@ -12,7 +13,7 @@ export class Type {
|
|||
|
||||
export const Undefined = new Type(
|
||||
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 NotAType = new Type(t => true) // Danger, do not merge!
|
||||
NotAType._doNotMerge = true
|
||||
|
|
|
@ -309,39 +309,41 @@ export class TypeDispatcher {
|
|||
}
|
||||
|
||||
let theBehavior = () => undefined
|
||||
this.resolve._genDepsOf.push([key, types]) // Important: make sure
|
||||
// not to return without popping _genDepsOf
|
||||
if (!('returns' in item)) {
|
||||
// looks like a factory
|
||||
try {
|
||||
theBehavior = item(
|
||||
DependencyRecorder(this, '', this, []),
|
||||
matched(template))
|
||||
} catch (e) {
|
||||
e.message = `Error in factory for ${key} on ${types} `
|
||||
+ `(match data ${template}): ${e.message}`
|
||||
throw e
|
||||
}
|
||||
} else theBehavior = item
|
||||
let finalBehavior
|
||||
this.resolve._genDepsOf.push([key, types])
|
||||
try { // Used to make sure not to return without popping _genDepsOf
|
||||
if (!('returns' in item)) {
|
||||
// looks like a factory
|
||||
try {
|
||||
theBehavior = item(
|
||||
DependencyRecorder(this, '', this, []),
|
||||
matched(template))
|
||||
} catch (e) {
|
||||
e.message = `Error in factory for ${key} on ${types} `
|
||||
+ `(match data ${template}): ${e.message}`
|
||||
throw e
|
||||
}
|
||||
} else theBehavior = item
|
||||
|
||||
let finalBehavior = theBehavior
|
||||
if (typeof theBehavior === 'function') {
|
||||
const returning = theBehavior.returns
|
||||
if (!returning) {
|
||||
throw new TypeError(
|
||||
`No return type specified for ${key} on ${types}`)
|
||||
}
|
||||
if (needsCollection(template)) {
|
||||
// have to wrap the behavior to collect the actual arguments
|
||||
// in the way corresponding to the template. Generating that
|
||||
// argument transformer may generate more dependencies.
|
||||
const morph = this._generateCollectFunction(template)
|
||||
finalBehavior =
|
||||
Returns(returning, (...args) => theBehavior(...morph(args)))
|
||||
finalBehavior = theBehavior
|
||||
if (typeof theBehavior === 'function') {
|
||||
const returning = theBehavior.returns
|
||||
if (!returning) {
|
||||
throw new TypeError(
|
||||
`No return type specified for ${key} on ${types}`)
|
||||
}
|
||||
if (needsCollection(template)) {
|
||||
// have to wrap the behavior to collect the actual arguments
|
||||
// in the way corresponding to the template. Generating that
|
||||
// argument transformer may generate more dependencies.
|
||||
const morph = this._generateCollectFunction(template)
|
||||
finalBehavior =
|
||||
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)
|
||||
finalBehavior.template = template
|
||||
return finalBehavior
|
||||
|
|
|
@ -11,4 +11,14 @@ describe('core type utility functions', () => {
|
|||
assert.strictEqual(math.one(undefined), undefined)
|
||||
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 {NotAType, Returns, TypeOfTypes} from '#core/Type.js'
|
||||
import {Any} from "#core/TypePatterns.js"
|
||||
import {boolnum} from "#number/helpers.js"
|
||||
|
||||
export const zero = onType(
|
||||
Any, (math, T) => {
|
||||
|
@ -24,3 +25,23 @@ export const one = onType(
|
|||
`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(-Infinity, 7), -1)
|
||||
assert(isNaN(compare(NaN, 0)))
|
||||
assert(isNaN(compare(false, NaN)))
|
||||
assert.throws(() => compare(false, NaN), TypeError)
|
||||
assert(isNaN(compare(NaN, NaN)))
|
||||
assert.strictEqual(compare(true, false), 1)
|
||||
assert.throws(() => compare(true, false), TypeError)
|
||||
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 {Any, matched} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
export const equal = onType([Any, Any], (math, [T, U]) => {
|
||||
// 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]) => {
|
||||
const eq = math.equal.resolve([T, U])
|
||||
const gt = math.exceeds.resolve([T, U])
|
||||
const isTnan = math.isnan.resolve([T])
|
||||
const isUnan = math.isnan.resolve([U])
|
||||
return Returns(NumberT, (t, u) => {
|
||||
if (isTnan(t) || isUnan(u)) return NaN
|
||||
if (eq(t,u)) return 0
|
||||
return gt(t, u) ? 1 : -1
|
||||
const zero = math.zero(T) // asymmetry here is unfortunate, but we have
|
||||
// to pick some argument's zero to use, so the first argument seemed
|
||||
// the most reasonable.
|
||||
const one = math.one(T)
|
||||
const negOne = math.negate(one)
|
||||
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 {onType} from '#core/helpers.js'
|
||||
import {onType, ResolutionError} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {Any} from "#core/TypePatterns.js"
|
||||
|
||||
export const isZero = (math, [T]) => {
|
||||
if (!T) { // called with no arguments
|
||||
throw new ResolutionError('isZero() requires one argument')
|
||||
}
|
||||
const z = math.zero(T)
|
||||
const eq = math.equal.resolve([T, T])
|
||||
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', {
|
||||
from: onType(BooleanT, math => math.number.resolve([BooleanT])),
|
||||
one: 1,
|
||||
zero: 0
|
||||
zero: 0,
|
||||
nan: NaN
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue