feat: factories can depend on the presence of types
All checks were successful
/ test (pull_request) Successful in 17s
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:
parent
a7673216c1
commit
b2b41d6348
8 changed files with 43 additions and 16 deletions
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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)))
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue