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.quotient(5, 3), 1)
|
||||||
assert.strictEqual(math.square(-3), 9)
|
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'
|
import {onType} from './helpers.js'
|
||||||
|
|
||||||
const typeObject = {} // have to make sure there is only one
|
|
||||||
|
|
||||||
export const types = () => typeObject
|
|
||||||
|
|
||||||
export class Type {
|
export class Type {
|
||||||
constructor(f, options = {}) {
|
constructor(f, options = {}) {
|
||||||
this.test = f
|
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 Returns = (type, f) => (f.returns = type, f)
|
||||||
|
|
||||||
export const typeOf = Returns(TypeOfTypes, item => {
|
export const whichType = typs => Returns(TypeOfTypes, item => {
|
||||||
for (const type of Object.values(typeObject)) {
|
for (const type of Object.values(typs)) {
|
||||||
if (!(type instanceof Type)) continue
|
if (!(type instanceof Type)) continue
|
||||||
if (type.test(item)) return type
|
if (type.test(item)) return type
|
||||||
}
|
}
|
||||||
|
@ -34,10 +30,12 @@ export const typeOf = Returns(TypeOfTypes, item => {
|
||||||
throw new TypeError(errorMsg)
|
throw new TypeError(errorMsg)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const typeOf = math => whichType(math.types)
|
||||||
|
|
||||||
// bootstrapping order matters, but order of exports in a module isn't
|
// 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
|
// simply the order that the items are listed in the module. So we make
|
||||||
// an explicitly ordered export of implementations for this sake:
|
// an explicitly ordered export of implementations for this sake:
|
||||||
|
|
||||||
export const bootstrapTypes = {
|
export const bootstrapTypes = {
|
||||||
types, Type, Undefined, TypeOfTypes, typeOf
|
Type, Undefined, TypeOfTypes, typeOf
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,18 @@ import ArrayKeyedMap from 'array-keyed-map'
|
||||||
import {
|
import {
|
||||||
Implementations, isPlainFunction, isPlainObject, onType
|
Implementations, isPlainFunction, isPlainObject, onType
|
||||||
} from './helpers.js'
|
} 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'
|
import {matched, needsCollection, Passthru} from './TypePatterns.js'
|
||||||
|
|
||||||
export class TypeDispatcher {
|
export class TypeDispatcher {
|
||||||
constructor(...specs) {
|
constructor(...specs) {
|
||||||
|
this._types = {} // stores all the types registered in this dispatcher
|
||||||
this._implementations = {} // maps key to list of [pattern, result] pairs
|
this._implementations = {} // maps key to list of [pattern, result] pairs
|
||||||
this._dependencies = {} // maps key to a map from type vectors to...
|
this._dependencies = {} // maps key to a map from type vectors to...
|
||||||
// ...a set of [key, types] that depend on it.
|
// ...a set of [key, types] that depend on it.
|
||||||
this._behaviors = {} // maps key to a map from type vectors to results
|
this._behaviors = {} // maps key to a map from type vectors to results
|
||||||
this._fallbacks = {} // maps key to a catchall result
|
this._fallbacks = {} // maps key to a catchall result
|
||||||
|
this.merge({types: () => this._types})
|
||||||
this.merge(bootstrapTypes) // bootstrap the instance
|
this.merge(bootstrapTypes) // bootstrap the instance
|
||||||
for (const spec of specs) this.merge(spec)
|
for (const spec of specs) this.merge(spec)
|
||||||
}
|
}
|
||||||
|
@ -73,9 +75,10 @@ export class TypeDispatcher {
|
||||||
// Redefine the property according to what sort of
|
// Redefine the property according to what sort of
|
||||||
// entity it is:
|
// entity it is:
|
||||||
if (isPlainFunction(tryValue)) {
|
if (isPlainFunction(tryValue)) {
|
||||||
|
const thisTypeOf = whichType(this.types)
|
||||||
// the usual case: a method of the dispatcher
|
// the usual case: a method of the dispatcher
|
||||||
const standard = (...args) => {
|
const standard = (...args) => {
|
||||||
const types = args.map(typeOf)
|
const types = args.map(thisTypeOf)
|
||||||
return this.resolve(key, types)(...args)
|
return this.resolve(key, types)(...args)
|
||||||
}
|
}
|
||||||
standard.resolve = (types) => this.resolve(key, types)
|
standard.resolve = (types) => this.resolve(key, types)
|
||||||
|
@ -320,7 +323,11 @@ const DependencyRecorder = (object, path, repo) => new Proxy(object, {
|
||||||
get(target, prop, receiver) {
|
get(target, prop, receiver) {
|
||||||
const result = Reflect.get(target, prop, receiver)
|
const result = Reflect.get(target, prop, receiver)
|
||||||
// pass resolve calls through, since we record dependencies therein:
|
// 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
|
// 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:
|
// 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)
|
repo._addToDeps(key, subpath)
|
||||||
// Now, if the result is an object, we may need to record further
|
// Now, if the result is an object, we may need to record further
|
||||||
// dependencies on its properties (e.g. math.config.predictable)
|
// dependencies on its properties (e.g. math.config.predictable)
|
||||||
// So proxy the return value:
|
// So proxy the return value, except for types, which must maintain
|
||||||
if (typeof result === 'object') {
|
// strict referential identity:
|
||||||
|
if (typeof result === 'object' && !(result instanceof Type)) {
|
||||||
return DependencyRecorder(result, newPath, repo)
|
return DependencyRecorder(result, newPath, repo)
|
||||||
} else return result
|
} else return result
|
||||||
}
|
}
|
||||||
|
@ -358,7 +366,7 @@ const DependencyWatcher = (object, path, repo) => new Proxy(object, {
|
||||||
get(target, prop, receiver) {
|
get(target, prop, receiver) {
|
||||||
// Only thing we need to do is push the watching down
|
// Only thing we need to do is push the watching down
|
||||||
const result = Reflect.get(target, prop, receiver)
|
const result = Reflect.get(target, prop, receiver)
|
||||||
if (typeof result === 'object') {
|
if (typeof result === 'object' && !(result instanceof Type)) {
|
||||||
const newPath = path.slice()
|
const newPath = path.slice()
|
||||||
newPath.push(prop)
|
newPath.push(prop)
|
||||||
return DependencyWatcher(result, newPath, repo)
|
return DependencyWatcher(result, newPath, repo)
|
||||||
|
|
|
@ -40,6 +40,7 @@ describe('TypeDispatcher', () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
incremental.add.resolve([Undefined, NumberT]).returns,
|
incremental.add.resolve([Undefined, NumberT]).returns,
|
||||||
NumberT)
|
NumberT)
|
||||||
|
assert.strictEqual(incremental.isnan(NaN), 1)
|
||||||
})
|
})
|
||||||
it('changes methods when their dependencies change', () => {
|
it('changes methods when their dependencies change', () => {
|
||||||
const gnmath = new TypeDispatcher(generics, numbers)
|
const gnmath = new TypeDispatcher(generics, numbers)
|
||||||
|
|
|
@ -5,4 +5,9 @@ describe('number utilities', () => {
|
||||||
it('clones a number', () => {
|
it('clones a number', () => {
|
||||||
assert.strictEqual(math.clone(2.637), 2.637)
|
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(
|
export const plain = f => onType(
|
||||||
Array(f.length).fill(NumberT), Returns(NumberT, f))
|
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 {BooleanT} from '#boolean/BooleanT.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {NumberT} from '#number/NumberT.js'
|
import {NumberT} from '#number/NumberT.js'
|
||||||
|
@ -7,6 +7,6 @@ const num = f => Returns(NumberT, f)
|
||||||
|
|
||||||
export const number = plain(a => a)
|
export const number = plain(a => a)
|
||||||
number.also(
|
number.also(
|
||||||
BooleanT, num(a => a ? 1 : 0),
|
BooleanT, boolnum,
|
||||||
[], num(() => 0)
|
[], 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 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
Add a link
Reference in a new issue