feat: factories can depend on the presence of types
All checks were successful
/ test (pull_request) Successful in 17s

Refactors each TypeDispatcher to have its own separate collection
  of types. Add isnan function which returns a boolean if that type
  is present, otherwise returns the number 1 for true, and 0 for fase.
This commit is contained in:
Glen Whitney 2025-04-13 01:35:10 -07:00
parent a7673216c1
commit b2b41d6348
8 changed files with 43 additions and 16 deletions

View file

@ -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)
})
})

View file

@ -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
}

View file

@ -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)

View file

@ -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)

View file

@ -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)
})
})

View file

@ -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)

View file

@ -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)
)

View file

@ -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)))
})