* Defines a BooleanT type * adds options to the Type constructor, so far just to allow conversions from other types * renames Number type to NumberT * records the name of types, and puts the name in the string representation * defines a conversion fron BooleanT to NumberT, specified to be automatic * stub code for automatic conversions, not yet complete * BooleanT not yet added to nanomath Checked that the new facilities do not disrupt the prior behavior on numbers.
This commit is contained in:
parent
14011984a0
commit
5bee93dbb3
16 changed files with 123 additions and 62 deletions
src
3
src/boolean/BooleanT.js
Normal file
3
src/boolean/BooleanT.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import {Type} from '#core/Type.js'
|
||||
|
||||
export const BooleanT = new Type(n => typeof n === 'boolean')
|
|
@ -3,8 +3,13 @@ const typeObject = {} // have to make sure there is only one
|
|||
export const types = () => typeObject
|
||||
|
||||
export class Type {
|
||||
constructor(f) {
|
||||
constructor(f, options = {}) {
|
||||
this.test = f
|
||||
this.from = options.from ?? []
|
||||
this.from.push(this)
|
||||
}
|
||||
toString() {
|
||||
return this.name || `[Type ${this.test}]`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,12 @@ import {typeOf, Type, bootstrapTypes} from './Type.js'
|
|||
import {
|
||||
Implementations, isPlainFunction, isPlainObject, onType
|
||||
} from './helpers.js'
|
||||
import {alwaysMatches, Any, Multiple} from './TypePatterns.js'
|
||||
import {
|
||||
alwaysMatches, matched, needsCollection, Any, Multiple
|
||||
} from './TypePatterns.js'
|
||||
|
||||
// helper that organizes a list into the same chunks as a pattern is
|
||||
// NOTE NOTE: need to update to handle conversions!
|
||||
const collectLike = (list, pattern, state = {pos: 0}) => {
|
||||
const result = []
|
||||
for (const elt of pattern) {
|
||||
|
@ -41,6 +44,7 @@ export class TypeDispatcher {
|
|||
if (val instanceof Type) {
|
||||
// TODO: Need to wipe out any dependencies on types[key]!
|
||||
this.types[key] = val
|
||||
val.name = key
|
||||
continue
|
||||
}
|
||||
if (typeof val === 'function') {
|
||||
|
@ -161,13 +165,16 @@ export class TypeDispatcher {
|
|||
let pattern
|
||||
let template
|
||||
if (imps.length) {
|
||||
for ([pattern, item] of imps) {
|
||||
let finalIndex
|
||||
;[finalIndex, template] = pattern.match(types)
|
||||
if (finalIndex === types.length) {
|
||||
needItem = false
|
||||
break
|
||||
for (const options of [{}, {convert: true}]) {
|
||||
for ([pattern, item] of imps) {
|
||||
let finalIndex
|
||||
;[finalIndex, template] = pattern.match(types, options)
|
||||
if (finalIndex === types.length) {
|
||||
needItem = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!needItem) break
|
||||
}
|
||||
}
|
||||
if (needItem && key in this._fallbacks) {
|
||||
|
@ -190,7 +197,8 @@ export class TypeDispatcher {
|
|||
this.resolve._genDepsOf.push([key, types])
|
||||
let theBehavior = () => undefined
|
||||
try {
|
||||
theBehavior = item(DependencyRecorder(this, [], this), template)
|
||||
theBehavior = item(
|
||||
DependencyRecorder(this, [], this), matched(template))
|
||||
} catch {
|
||||
behave.set(types, item)
|
||||
return item
|
||||
|
@ -199,8 +207,7 @@ export class TypeDispatcher {
|
|||
}
|
||||
if (typeof theBehavior === 'function'
|
||||
&& theBehavior.length
|
||||
&& Array.isArray(template)
|
||||
&& template.some(elt => Array.isArray(elt))
|
||||
&& needsCollection(template)
|
||||
) {
|
||||
// have to wrap the behavior to collect the actual arguments
|
||||
// in the way corresponding to the template
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Type, Undefined} from './Type.js'
|
||||
|
||||
export class TypePattern {
|
||||
match(typeSequence, position = 0) {
|
||||
match(typeSequence, options={}) {
|
||||
throw new Error('Specific TypePatterns must implement match')
|
||||
}
|
||||
sampleTypes() {
|
||||
|
@ -15,10 +15,16 @@ class MatchTypePattern extends TypePattern {
|
|||
super()
|
||||
this.type = typeToMatch
|
||||
}
|
||||
match(typeSequence, position = 0) {
|
||||
match(typeSequence, options={}) {
|
||||
const position = options.position ?? 0
|
||||
const allowed = options.convert ? this.type.from : [this.type]
|
||||
if (position < typeSequence.length
|
||||
&& typeSequence[position] === this.type
|
||||
&& allowed.includes(typeSequence[position])
|
||||
) {
|
||||
let templateItem = typeSequence[position]
|
||||
if (templateItem !== this.type) {
|
||||
templateItem = {actual: templateItem, matched: this.type}
|
||||
}
|
||||
return [position + 1, typeSequence[position]]
|
||||
}
|
||||
return [-1, Undefined]
|
||||
|
@ -32,15 +38,19 @@ class SequencePattern extends TypePattern {
|
|||
super()
|
||||
this.patterns = itemsToMatch.map(pattern)
|
||||
}
|
||||
match(typeSequence, position = 0) {
|
||||
match(typeSequence, options={_internal: true}) {
|
||||
options = options._internal
|
||||
? options
|
||||
: Object.assign({_internal: true}, options)
|
||||
options.position ??= 0
|
||||
const matches = []
|
||||
for (const pat of this.patterns) {
|
||||
const [newPos, newMatch] = pat.match(typeSequence, position)
|
||||
const [newPos, newMatch] = pat.match(typeSequence, options)
|
||||
if (newPos < 0) return [-1, Undefined]
|
||||
position = newPos
|
||||
options.position = newPos
|
||||
matches.push(newMatch)
|
||||
}
|
||||
return [position, matches]
|
||||
return [options.position, matches]
|
||||
}
|
||||
sampleTypes() {
|
||||
return this.patterns.map(pat => pat.sampleTypes()).flat()
|
||||
|
@ -62,7 +72,8 @@ export const pattern = patternOrSpec => {
|
|||
}
|
||||
|
||||
class AnyPattern extends TypePattern {
|
||||
match(typeSequence, position = 0) {
|
||||
match(typeSequence, options={}) {
|
||||
const position = options.position ?? 0
|
||||
return position < typeSequence.length
|
||||
? [position + 1, typeSequence[position]]
|
||||
: [-1, Undefined]
|
||||
|
@ -77,14 +88,18 @@ class OptionalPattern extends TypePattern {
|
|||
super()
|
||||
this.pattern = pattern(item)
|
||||
}
|
||||
match(typeSequence, position = 0) {
|
||||
match(typeSequence, options={_internal: true}) {
|
||||
options = options._internal
|
||||
? options
|
||||
: Object.assign({_internal: true}, options)
|
||||
options.position ??= 0
|
||||
const matches = []
|
||||
const [newPos, newMatch] = this.pattern.match(typeSequence, position)
|
||||
const [newPos, newMatch] = this.pattern.match(typeSequence, options)
|
||||
if (newPos >= 0) {
|
||||
position = newPos
|
||||
options.position = newPos
|
||||
matches.push(newMatch)
|
||||
}
|
||||
return [position, matches]
|
||||
return [options.position, matches]
|
||||
}
|
||||
sampleTypes() {return []}
|
||||
equal(other) {
|
||||
|
@ -99,12 +114,16 @@ class MultiPattern extends TypePattern {
|
|||
super()
|
||||
this.pattern = pattern(item)
|
||||
}
|
||||
match(typeSequence, position = 0) {
|
||||
match(typeSequence, options={_internal: true}) {
|
||||
options = options._internal
|
||||
? options
|
||||
: Object.assign({_internal: true}, options)
|
||||
options.position ??= 0
|
||||
const matches = []
|
||||
while (true) {
|
||||
const [newPos, newMatch] = this.pattern.match(typeSequence, position)
|
||||
if (newPos < 0) return [position, matches]
|
||||
position = newPos
|
||||
const [newPos, newMatch] = this.pattern.match(typeSequence, options)
|
||||
if (newPos < 0) return [options.position, matches]
|
||||
options.position = newPos
|
||||
matches.push(newMatch)
|
||||
}
|
||||
}
|
||||
|
@ -117,3 +136,18 @@ class MultiPattern extends TypePattern {
|
|||
export const Multiple = item => new MultiPattern(item)
|
||||
export const alwaysMatches =
|
||||
pat => pat instanceof MultiPattern && pat.pattern instanceof AnyPattern
|
||||
|
||||
// returns the template just of matched types, dropping any actual types
|
||||
export const matched = (template) => {
|
||||
if (Array.isArray(template)) return template.map(matched)
|
||||
return template.matched ?? template
|
||||
}
|
||||
|
||||
// checks if the template is just pass-through or needs collection
|
||||
export const needsCollection = (template) => {
|
||||
if (Array.isArray(template)) {
|
||||
return template.some(
|
||||
elt => Array.isArray(elt) || needsCollection(elt))
|
||||
}
|
||||
return 'actual' in template
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {Number} from '#number/Number.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
import {Returns} from '../Type.js'
|
||||
import {isPlainFunction} from '../helpers.js'
|
||||
|
||||
describe('Core types', () => {
|
||||
it('creates an object with all of the types', () => {
|
||||
assert('Number' in math.types)
|
||||
assert.strictEqual(math.types.Number, Number)
|
||||
assert('NumberT' in math.types)
|
||||
assert.strictEqual(math.types.NumberT, NumberT)
|
||||
assert('Undefined' in math.types)
|
||||
assert(!('Type' in math.types))
|
||||
assert(math.types.Undefined.test(undefined))
|
||||
|
@ -18,9 +18,9 @@ describe('Core types', () => {
|
|||
|
||||
it('supports a typeOf operator', () => {
|
||||
const tO = math.typeOf
|
||||
assert.strictEqual(tO(7), Number)
|
||||
assert.strictEqual(tO(7), NumberT)
|
||||
assert.strictEqual(tO(undefined), math.types.Undefined)
|
||||
assert.strictEqual(tO(Number), math.types.TypeOfTypes)
|
||||
assert.strictEqual(tO(NumberT), math.types.TypeOfTypes)
|
||||
assert.throws(() => tO(Symbol()), TypeError)
|
||||
})
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ describe('TypeDispatcher', () => {
|
|||
assert.strictEqual(
|
||||
incremental.typeOf(undefined), incremental.types.Undefined)
|
||||
incremental.merge(numbers)
|
||||
const {Number, TypeOfTypes, Undefined} = incremental.types
|
||||
assert(Number.test(7))
|
||||
const {NumberT, TypeOfTypes, Undefined} = incremental.types
|
||||
assert(NumberT.test(7))
|
||||
assert.strictEqual(incremental.add(-1.5, 0.5), -1)
|
||||
// Make Undefined act like zero:
|
||||
incremental.merge({add: onType(
|
||||
|
@ -27,18 +27,18 @@ describe('TypeDispatcher', () => {
|
|||
TypeOfTypes)
|
||||
assert.strictEqual(incremental.add(undefined, -3.25), -3.25)
|
||||
assert.strictEqual(
|
||||
incremental.add.resolve([Undefined, Number]).returns,
|
||||
Number)
|
||||
incremental.add.resolve([Undefined, NumberT]).returns,
|
||||
NumberT)
|
||||
// Oops, changed my mind, make it work like NaN with numbers:
|
||||
const alwaysNaN = Returns(Number, () => NaN)
|
||||
const alwaysNaN = Returns(NumberT, () => NaN)
|
||||
incremental.merge({add: onType(
|
||||
[Undefined, Number], alwaysNaN,
|
||||
[Number, Undefined], alwaysNaN
|
||||
[Undefined, NumberT], alwaysNaN,
|
||||
[NumberT, Undefined], alwaysNaN
|
||||
)})
|
||||
assert(isNaN(incremental.add(undefined, -3.25)))
|
||||
assert.strictEqual(
|
||||
incremental.add.resolve([Undefined, Number]).returns,
|
||||
Number)
|
||||
incremental.add.resolve([Undefined, NumberT]).returns,
|
||||
NumberT)
|
||||
})
|
||||
it('changes methods when their dependencies change', () => {
|
||||
const gnmath = new TypeDispatcher(generics, numbers)
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import {Type} from './Type.js'
|
||||
import {pattern} from './TypePatterns.js'
|
||||
|
||||
export class Implementations {
|
||||
constructor(imps) {
|
||||
this.patterns = new Map()
|
||||
this._add(imps)
|
||||
}
|
||||
_add(imps) {
|
||||
for (let i = 0; i < imps.length; ++i) {
|
||||
this.patterns.set(pattern(imps[i]), imps[++i])
|
||||
}
|
||||
}
|
||||
also(...imps) {
|
||||
this._add(imps)
|
||||
}
|
||||
}
|
||||
|
||||
export const onType = (...imps) => new Implementations(imps)
|
||||
|
|
|
@ -5,7 +5,7 @@ describe('generic arithmetic', () => {
|
|||
it('squares anything', () => {
|
||||
assert.strictEqual(math.square(7), 49)
|
||||
assert.strictEqual(
|
||||
math.square.resolve([math.types.Number]).returns,
|
||||
math.types.Number)
|
||||
math.square.resolve([math.types.NumberT]).returns,
|
||||
math.types.NumberT)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import {Type} from '#core/Type.js'
|
||||
|
||||
export const Number = new Type(n => typeof n === 'number')
|
6
src/number/NumberT.js
Normal file
6
src/number/NumberT.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import {Type} from '#core/Type.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
|
||||
export const NumberT = new Type(n => typeof n === 'number', {
|
||||
from: [BooleanT]
|
||||
})
|
|
@ -1,11 +0,0 @@
|
|||
import assert from 'assert'
|
||||
import {Number} from '../Number.js'
|
||||
|
||||
describe('Number Type', () => {
|
||||
it('correctly recognizes numbers', () => {
|
||||
assert(Number.test(3))
|
||||
assert(Number.test(NaN))
|
||||
assert(Number.test(Infinity))
|
||||
assert(!Number.test("3"))
|
||||
})
|
||||
})
|
11
src/number/__test__/NumberT.spec.js
Normal file
11
src/number/__test__/NumberT.spec.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import assert from 'assert'
|
||||
import {NumberT} from '../NumberT.js'
|
||||
|
||||
describe('NumberT Type', () => {
|
||||
it('correctly recognizes numbers', () => {
|
||||
assert(NumberT.test(3))
|
||||
assert(NumberT.test(NaN))
|
||||
assert(NumberT.test(Infinity))
|
||||
assert(!NumberT.test("3"))
|
||||
})
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
export * as typeDefinition from './Number.js'
|
||||
export * as typeDefinition from './NumberT.js'
|
||||
export * as arithmetic from './arithmetic.js'
|
||||
export * as type from './type.js'
|
||||
export * as utils from './utils.js'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Number} from './Number.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
|
||||
import {onType} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
|
||||
export const plain = f => onType(
|
||||
Array(f.length).fill(Number), Returns(Number, f))
|
||||
Array(f.length).fill(NumberT), Returns(NumberT, f))
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import {plain} from './helpers.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
// Not much to do so far when there is only one type
|
||||
export const number = plain(a => a)
|
||||
number.also(BooleanT, Returns(NumberT, a => a ? 1 : 0))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"imports" : {
|
||||
"#nanomath": "./nanomath.js",
|
||||
"#boolean/*.js": "./boolean/*.js",
|
||||
"#core/*.js": "./core/*.js",
|
||||
"#number/*.js": "./number/*.js"
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue