diff --git a/src/__test__/numbers.spec.js b/src/__test__/numbers.spec.js index effd3dd..95f3658 100644 --- a/src/__test__/numbers.spec.js +++ b/src/__test__/numbers.spec.js @@ -6,4 +6,8 @@ describe('the numbers-only bundle', () => { assert.strictEqual(math.quotient(5, 3), 1) assert.strictEqual(math.square(-3), 9) }) + it('uses 1 and 0 instead of true and false', () => { + assert.strictEqual(math.isnan(-16.5), 0) + assert.strictEqual(math.isnan(NaN), 1) + }) }) diff --git a/src/core/Type.js b/src/core/Type.js index 70e1ed1..18530bc 100644 --- a/src/core/Type.js +++ b/src/core/Type.js @@ -1,9 +1,5 @@ import {onType} from './helpers.js' -const typeObject = {} // have to make sure there is only one - -export const types = () => typeObject - export class Type { constructor(f, options = {}) { this.test = f @@ -20,8 +16,8 @@ export const TypeOfTypes = new Type(t => t instanceof Type) export const Returns = (type, f) => (f.returns = type, f) -export const typeOf = Returns(TypeOfTypes, item => { - for (const type of Object.values(typeObject)) { +export const whichType = typs => Returns(TypeOfTypes, item => { + for (const type of Object.values(typs)) { if (!(type instanceof Type)) continue if (type.test(item)) return type } @@ -34,10 +30,12 @@ export const typeOf = Returns(TypeOfTypes, item => { throw new TypeError(errorMsg) }) +export const typeOf = 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 = { - types, Type, Undefined, TypeOfTypes, typeOf + Type, Undefined, TypeOfTypes, typeOf } diff --git a/src/core/TypeDispatcher.js b/src/core/TypeDispatcher.js index 8c495d5..963f033 100644 --- a/src/core/TypeDispatcher.js +++ b/src/core/TypeDispatcher.js @@ -3,16 +3,18 @@ import ArrayKeyedMap from 'array-keyed-map' import { Implementations, isPlainFunction, isPlainObject, onType } from './helpers.js' -import {bootstrapTypes, Returns, typeOf, Type} from './Type.js' +import {bootstrapTypes, Returns, whichType, Type} from './Type.js' import {matched, needsCollection, Passthru} from './TypePatterns.js' export class TypeDispatcher { constructor(...specs) { + this._types = {} // stores all the types registered in this dispatcher this._implementations = {} // maps key to list of [pattern, result] pairs this._dependencies = {} // maps key to a map from type vectors to... // ...a set of [key, types] that depend on it. this._behaviors = {} // maps key to a map from type vectors to results this._fallbacks = {} // maps key to a catchall result + this.merge({types: () => this._types}) this.merge(bootstrapTypes) // bootstrap the instance for (const spec of specs) this.merge(spec) } @@ -73,9 +75,10 @@ export class TypeDispatcher { // Redefine the property according to what sort of // entity it is: if (isPlainFunction(tryValue)) { + const thisTypeOf = whichType(this.types) // the usual case: a method of the dispatcher const standard = (...args) => { - const types = args.map(typeOf) + const types = args.map(thisTypeOf) return this.resolve(key, types)(...args) } standard.resolve = (types) => this.resolve(key, types) @@ -320,7 +323,11 @@ const DependencyRecorder = (object, path, repo) => new Proxy(object, { get(target, prop, receiver) { const result = Reflect.get(target, prop, receiver) // pass resolve calls through, since we record dependencies therein: - if (prop === 'resolve' || 'isDispatcher' in result) return result + if (prop === 'resolve' + || (typeof result === 'function' && 'isDispatcher' in result) + ) { + return result + } // OK, it's not a method on a TypeDispatcher, it's some other kind of // value. So first record the dependency on prop at this path: @@ -331,8 +338,9 @@ const DependencyRecorder = (object, path, repo) => new Proxy(object, { repo._addToDeps(key, subpath) // Now, if the result is an object, we may need to record further // dependencies on its properties (e.g. math.config.predictable) - // So proxy the return value: - if (typeof result === 'object') { + // So proxy the return value, except for types, which must maintain + // strict referential identity: + if (typeof result === 'object' && !(result instanceof Type)) { return DependencyRecorder(result, newPath, repo) } else return result } @@ -358,7 +366,7 @@ const DependencyWatcher = (object, path, repo) => new Proxy(object, { get(target, prop, receiver) { // Only thing we need to do is push the watching down const result = Reflect.get(target, prop, receiver) - if (typeof result === 'object') { + if (typeof result === 'object' && !(result instanceof Type)) { const newPath = path.slice() newPath.push(prop) return DependencyWatcher(result, newPath, repo) diff --git a/src/core/__test__/TypeDispatcher.spec.js b/src/core/__test__/TypeDispatcher.spec.js index 62a997e..8c5bf81 100644 --- a/src/core/__test__/TypeDispatcher.spec.js +++ b/src/core/__test__/TypeDispatcher.spec.js @@ -40,6 +40,7 @@ describe('TypeDispatcher', () => { assert.strictEqual( incremental.add.resolve([Undefined, NumberT]).returns, NumberT) + assert.strictEqual(incremental.isnan(NaN), 1) }) it('changes methods when their dependencies change', () => { const gnmath = new TypeDispatcher(generics, numbers) diff --git a/src/number/__test__/utils.spec.js b/src/number/__test__/utils.spec.js index 50651b0..054a30a 100644 --- a/src/number/__test__/utils.spec.js +++ b/src/number/__test__/utils.spec.js @@ -5,4 +5,9 @@ describe('number utilities', () => { it('clones a number', () => { assert.strictEqual(math.clone(2.637), 2.637) }) + it('tests if a number is NaN', () => { + assert.strictEqual(math.isnan(NaN), true) + assert.strictEqual(math.isnan(Infinity), false) + assert.strictEqual(math.isnan(43), false) + }) }) diff --git a/src/number/helpers.js b/src/number/helpers.js index d082667..faee3ed 100644 --- a/src/number/helpers.js +++ b/src/number/helpers.js @@ -5,3 +5,5 @@ import {Returns} from '#core/Type.js' export const plain = f => onType( Array(f.length).fill(NumberT), Returns(NumberT, f)) + +export const boolnum = Returns(NumberT, p => p ? 1 : 0) diff --git a/src/number/type.js b/src/number/type.js index b7656ba..0c279da 100644 --- a/src/number/type.js +++ b/src/number/type.js @@ -1,4 +1,4 @@ -import {plain} from './helpers.js' +import {plain, boolnum} from './helpers.js' import {BooleanT} from '#boolean/BooleanT.js' import {Returns} from '#core/Type.js' import {NumberT} from '#number/NumberT.js' @@ -7,6 +7,6 @@ const num = f => Returns(NumberT, f) export const number = plain(a => a) number.also( - BooleanT, num(a => a ? 1 : 0), + BooleanT, boolnum, [], num(() => 0) ) diff --git a/src/number/utils.js b/src/number/utils.js index 783d6c3..06973df 100644 --- a/src/number/utils.js +++ b/src/number/utils.js @@ -1,3 +1,12 @@ -import {plain} from './helpers.js' +import {plain, boolnum} from './helpers.js' +import {NumberT} from './NumberT.js' + +import {Returns} from '#core/Type.js' +import {onType} from '#core/helpers.js' export const clone = plain(a => a) +export const isnan = onType(NumberT, math => { + const {BooleanT} = math.types + if (BooleanT) return Returns(BooleanT, a => isNaN(a)) + return Returns(NumberT, a => boolnum(isNaN(a))) +})