refactor: change onType to match and take only one pattern and result (#22)
All checks were successful
/ test (push) Successful in 17s
All checks were successful
/ test (push) Successful in 17s
Pursuant to #12. Besides changing the name of onType to match, and only allowing one pattern and result in `match()`, this PR also arranges that in place of an onType with lots of alternating PATTERN, VALUE, PATTERN, VALUE arguments, one now exports an _array_ of `match(PATTERN, VALUE)` items. Doesn't quite fully resolve #12, because there is still the question of whether `match(...)` can be left out for a behavior that literally matches anything (current behavior), or whether `match(Passthru, behavior)` should be required for such cases. Reviewed-on: #22 Co-authored-by: Glen Whitney <glen@studioinfinity.org> Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
parent
491e207fad
commit
236f46c0c0
22 changed files with 147 additions and 135 deletions
|
@ -1,14 +1,14 @@
|
||||||
import {BooleanT} from './BooleanT.js'
|
import {BooleanT} from './BooleanT.js'
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
|
import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
|
||||||
import {NumberT} from '#number/NumberT.js'
|
import {NumberT} from '#number/NumberT.js'
|
||||||
|
|
||||||
const bool = f => Returns(BooleanT, f)
|
const bool = f => Returns(BooleanT, f)
|
||||||
|
|
||||||
export const boolean = onType(
|
export const boolean = [
|
||||||
BooleanT, bool(p => p),
|
match(BooleanT, bool(p => p)),
|
||||||
NumberT, bool(a => !!a),
|
match(NumberT, bool(a => !!a)),
|
||||||
TypeOfTypes, bool(() => true),
|
match(TypeOfTypes, bool(() => true)),
|
||||||
Undefined, bool(() => false),
|
match(Undefined, bool(() => false)),
|
||||||
[], bool(() => false)
|
match([], bool(() => false))
|
||||||
)
|
]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Type} from '#core/Type.js'
|
import {Type} from '#core/Type.js'
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
|
|
||||||
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z
|
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z
|
||||||
|
|
||||||
|
@ -10,15 +10,17 @@ function complexSpecialize(ComponentType) {
|
||||||
const specTest = z => isComplex(z) && compTest(z.re) && compTest(z.im)
|
const specTest = z => isComplex(z) && compTest(z.re) && compTest(z.im)
|
||||||
const typeName = `Complex(${ComponentType})`
|
const typeName = `Complex(${ComponentType})`
|
||||||
if (ComponentType.concrete) {
|
if (ComponentType.concrete) {
|
||||||
const fromSpec = [
|
const fromSpec = [match(
|
||||||
ComponentType, math => r => ({re: r, im: ComponentType.zero})]
|
ComponentType, math => r => ({re: r, im: ComponentType.zero}))]
|
||||||
for (const [matchType, fctry] of ComponentType.from.patterns) {
|
for (const {pattern, does} of ComponentType.from) {
|
||||||
fromSpec.push(this.specialize(matchType.type), math => {
|
fromSpec.push(match(
|
||||||
const compConv = fctry(math)
|
this.specialize(pattern.type),
|
||||||
return z => ({re: compConv(z.re), im: compConv(z.im)})
|
math => {
|
||||||
})
|
const compConv = does(math)
|
||||||
|
return z => ({re: compConv(z.re), im: compConv(z.im)})
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
const typeOptions = {from: onType(...fromSpec), typeName}
|
const typeOptions = {from: fromSpec, typeName}
|
||||||
if ('zero' in ComponentType) {
|
if ('zero' in ComponentType) {
|
||||||
typeOptions.zero = {re: ComponentType.zero, im: ComponentType.zero}
|
typeOptions.zero = {re: ComponentType.zero, im: ComponentType.zero}
|
||||||
if ('one' in ComponentType) {
|
if ('one' in ComponentType) {
|
||||||
|
|
|
@ -21,11 +21,11 @@ describe('Complex Type', () => {
|
||||||
const convertImps = CplxNum.from
|
const convertImps = CplxNum.from
|
||||||
let cnvNtoCN
|
let cnvNtoCN
|
||||||
let cnvCBtoCN
|
let cnvCBtoCN
|
||||||
for (const [pattern, convFactory] of convertImps.patterns) {
|
for (const {pattern, does} of convertImps) {
|
||||||
if (pattern.match([NumberT])[0] === 1) {
|
if (pattern.match([NumberT])[0] === 1) {
|
||||||
cnvNtoCN = convFactory(math)
|
cnvNtoCN = does(math)
|
||||||
} else if (pattern.match([Complex(BooleanT)])[0] === 1) {
|
} else if (pattern.match([Complex(BooleanT)])[0] === 1) {
|
||||||
cnvCBtoCN = convFactory(math)
|
cnvCBtoCN = does(math)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.deepStrictEqual(cnvNtoCN(3.5), {re: 3.5, im: 0})
|
assert.deepStrictEqual(cnvNtoCN(3.5), {re: 3.5, im: 0})
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {Complex} from './Complex.js'
|
import {Complex} from './Complex.js'
|
||||||
import {onType} from "#core/helpers.js"
|
import {match} from "#core/helpers.js"
|
||||||
import {Returns} from "#core/Type.js"
|
import {Returns} from "#core/Type.js"
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {Any} from "#core/TypePatterns.js"
|
||||||
|
|
||||||
export const complex = onType(
|
export const complex = [
|
||||||
Any, (math, T) => {
|
match(Any, (math, T) => {
|
||||||
const z = math.zero(T)
|
const z = math.zero(T)
|
||||||
if (math.hasnan(T)) {
|
if (math.hasnan(T)) {
|
||||||
const isnan = math.isnan.resolve([T])
|
const isnan = math.isnan.resolve([T])
|
||||||
|
@ -15,8 +15,8 @@ export const complex = onType(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return Returns(Complex(T), r => ({re: r, im: z}))
|
return Returns(Complex(T), r => ({re: r, im: z}))
|
||||||
},
|
}),
|
||||||
[Any, Any], (math, [T, U]) => {
|
match([Any, Any], (math, [T, U]) => {
|
||||||
if (T !== U) {
|
if (T !== U) {
|
||||||
throw new RangeError(
|
throw new RangeError(
|
||||||
'mixed complex types disallowed '
|
'mixed complex types disallowed '
|
||||||
|
@ -24,5 +24,4 @@ export const complex = onType(
|
||||||
}
|
}
|
||||||
return Returns(Complex(T), (r, m) => ({re: r, im: m}))
|
return Returns(Complex(T), (r, m) => ({re: r, im: m}))
|
||||||
})
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,10 @@ export class Type extends Function {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.test = f
|
this.test = f
|
||||||
this.from = options.from ?? {patterns: []} // mock empty Implementations
|
// we want property `from` to end up as an array of Matchers:
|
||||||
|
this.from = options.from
|
||||||
|
? Array.isArray(options.from) ? options.from : [options.from]
|
||||||
|
: []
|
||||||
if ('zero' in options) this.zero = options.zero
|
if ('zero' in options) this.zero = options.zero
|
||||||
if ('one' in options) this.one = options.one
|
if ('one' in options) this.one = options.one
|
||||||
if ('nan' in options) this.nan = options.nan
|
if ('nan' in options) this.nan = options.nan
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import ArrayKeyedMap from 'array-keyed-map'
|
import ArrayKeyedMap from 'array-keyed-map'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Implementations, ImplementationsGenerator, ResolutionError,
|
Implementations, ImplementationsGenerator, Matcher, ResolutionError,
|
||||||
isPlainFunction, isPlainObject, onType, types
|
isPlainFunction, isPlainObject, match, types
|
||||||
} from './helpers.js'
|
} from './helpers.js'
|
||||||
import {bootstrapTypes, Returns, whichType, 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'
|
||||||
|
@ -45,11 +45,14 @@ export class TypeDispatcher {
|
||||||
// an Implementations that associates it with the Passthru pattern
|
// an Implementations that associates it with the Passthru pattern
|
||||||
// -- a factory for entities, to be invoked to get the value of a key
|
// -- a factory for entities, to be invoked to get the value of a key
|
||||||
// for any types. You can just merge that.
|
// for any types. You can just merge that.
|
||||||
|
// -- a value or behavior that applies just when the types of an
|
||||||
|
// argument match a type pattern. For that, you merge the result
|
||||||
|
// of `match(PATTERN, VALUE)`
|
||||||
// -- a collection of different values, or different behaviors, or
|
// -- a collection of different values, or different behaviors, or
|
||||||
// different factories for different types for a given key. For that
|
// different factories for different lists of argument types for a
|
||||||
// you merge an Implementations object that associates each item with
|
// given key. For that you merge an array of results of `match`,
|
||||||
// a TypePattern. An Implementation object can most easily be
|
// so something like
|
||||||
// generated with `onType(PATTERN, VALUE, PATTERN, VALUE,...)`
|
// `[match(PATTERN, VALUE), match(PATTERN, VALUE), ...]`
|
||||||
// Initially I thought those were all the possibilities. But then I
|
// Initially I thought those were all the possibilities. But then I
|
||||||
// wanted to export something that when merged, would set the Passthru
|
// wanted to export something that when merged, would set the Passthru
|
||||||
// pattern to a fresh specific object for that merge, but so that the
|
// pattern to a fresh specific object for that merge, but so that the
|
||||||
|
@ -57,10 +60,10 @@ export class TypeDispatcher {
|
||||||
// particular TypeDispatcher (this situation applies to the config object).
|
// particular TypeDispatcher (this situation applies to the config object).
|
||||||
// To produce that behavior, you need a fourth thing
|
// To produce that behavior, you need a fourth thing
|
||||||
// -- an ImplementationGenerator, which is basically a function that
|
// -- an ImplementationGenerator, which is basically a function that
|
||||||
// returns an Implementations object as above. As this is only needed
|
// returns one of the last couple of options above. As this variant is
|
||||||
// for a single entity that will be merged into multiple different
|
// only needed for a single entity that will be merged into multiple
|
||||||
// TypeDispatchers, there's not a big focus on making this convenient;
|
// different TypeDispatchers, there's not a big focus on making
|
||||||
// it's not expected to come up much.
|
// specifying it convenient; it's not expected to come up much.
|
||||||
|
|
||||||
merge(spec) {
|
merge(spec) {
|
||||||
if (!spec) return
|
if (!spec) return
|
||||||
|
@ -74,9 +77,7 @@ export class TypeDispatcher {
|
||||||
// For special cases like types, config, etc, we can wrap
|
// For special cases like types, config, etc, we can wrap
|
||||||
// a function in ImplementationsGenerator to produce the thing
|
// a function in ImplementationsGenerator to produce the thing
|
||||||
// we should really merge:
|
// we should really merge:
|
||||||
if (val instanceof ImplementationsGenerator) {
|
if (val instanceof ImplementationsGenerator) val = val.generate()
|
||||||
val = val.generate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now dispatch on what sort of thing we are supposed to merge:
|
// Now dispatch on what sort of thing we are supposed to merge:
|
||||||
if (val instanceof Type) {
|
if (val instanceof Type) {
|
||||||
|
@ -100,7 +101,14 @@ export class TypeDispatcher {
|
||||||
|
|
||||||
// Everything else we coerce into Implementations and deal with
|
// Everything else we coerce into Implementations and deal with
|
||||||
// right here:
|
// right here:
|
||||||
if (!(val instanceof Implementations)) val = onType(Passthru, val)
|
if (val instanceof Matcher) val = new Implementations(val)
|
||||||
|
if (Array.isArray(val)) val = new Implementations(val)
|
||||||
|
if (!(val instanceof Implementations)) {
|
||||||
|
val = new Implementations(match(Passthru, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// hereafter we may assume that val is an instance of Implementations
|
||||||
|
|
||||||
if (!(key in this)) {
|
if (!(key in this)) {
|
||||||
// Need to "bootstrap" the item:
|
// Need to "bootstrap" the item:
|
||||||
// We initially define it with a temporary getter, only
|
// We initially define it with a temporary getter, only
|
||||||
|
@ -189,11 +197,11 @@ export class TypeDispatcher {
|
||||||
this._behaviors[key] = new ArrayKeyedMap()
|
this._behaviors[key] = new ArrayKeyedMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now add all of the patterns of this implementation:
|
// Now add all of the matchers of this Implementations:
|
||||||
for (const [pattern, result] of val.patterns) {
|
for (const {pattern, does} of val.matchers) {
|
||||||
if (pattern === Passthru) {
|
if (pattern === Passthru) {
|
||||||
if (key in this._fallbacks) this._disengageFallback(key)
|
if (key in this._fallbacks) this._disengageFallback(key)
|
||||||
this._fallbacks[key] = result
|
this._fallbacks[key] = does
|
||||||
} else {
|
} else {
|
||||||
this._clearBehaviorsMatching(key, pattern)
|
this._clearBehaviorsMatching(key, pattern)
|
||||||
// if it happens the same pattern is already in the
|
// if it happens the same pattern is already in the
|
||||||
|
@ -201,7 +209,7 @@ export class TypeDispatcher {
|
||||||
const imps = this._implementations[key]
|
const imps = this._implementations[key]
|
||||||
const have = imps.findIndex(elt => pattern.equal(elt[0]))
|
const have = imps.findIndex(elt => pattern.equal(elt[0]))
|
||||||
if (have >= 0) imps.splice(have, 1)
|
if (have >= 0) imps.splice(have, 1)
|
||||||
this._implementations[key].unshift([pattern, result])
|
this._implementations[key].unshift([pattern, does])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class MatchTypePattern extends TypePattern {
|
||||||
if (position < typeSequence.length) {
|
if (position < typeSequence.length) {
|
||||||
if (this.type.specializesTo(actual)) return [position + 1, actual]
|
if (this.type.specializesTo(actual)) return [position + 1, actual]
|
||||||
if (options.convert) {
|
if (options.convert) {
|
||||||
for (const [pattern, convertor] of this.type.from.patterns) {
|
for (const {pattern, does: convertor} of this.type.from) {
|
||||||
const [pos] = pattern.match([actual])
|
const [pos] = pattern.match([actual])
|
||||||
if (pos === 1) {
|
if (pos === 1) {
|
||||||
return [position + 1, {actual, convertor, matched: this.type}]
|
return [position + 1, {actual, convertor, matched: this.type}]
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as booleans from '#boolean/all.js'
|
||||||
import * as generics from '#generic/all.js'
|
import * as generics from '#generic/all.js'
|
||||||
import * as numbers from '#number/all.js'
|
import * as numbers from '#number/all.js'
|
||||||
import {NumberT} from '#number/NumberT.js'
|
import {NumberT} from '#number/NumberT.js'
|
||||||
import {onType, ResolutionError} from "#core/helpers.js"
|
import {match, ResolutionError} from "#core/helpers.js"
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {Any} from "#core/TypePatterns.js"
|
||||||
import {Returns, NotAType} from "#core/Type.js"
|
import {Returns, NotAType} from "#core/Type.js"
|
||||||
import {plain} from "#number/helpers.js"
|
import {plain} from "#number/helpers.js"
|
||||||
|
@ -20,10 +20,10 @@ describe('TypeDispatcher', () => {
|
||||||
assert.strictEqual(incremental.add(-1.5, 0.5), -1)
|
assert.strictEqual(incremental.add(-1.5, 0.5), -1)
|
||||||
assert.throws(() => incremental.add(7, undefined), ResolutionError)
|
assert.throws(() => incremental.add(7, undefined), ResolutionError)
|
||||||
// Make Undefined act like zero:
|
// Make Undefined act like zero:
|
||||||
incremental.merge({add: onType(
|
incremental.merge({add: [
|
||||||
[Undefined, Any], (_m, [_U, T]) => Returns(T, (_a, b) => b),
|
match([Undefined, Any], (_m, [_U, T]) => Returns(T, (_a, b) => b)),
|
||||||
[Any, Undefined], (_m, [T]) => Returns(T, a => a)
|
match([Any, Undefined], (_m, [T]) => Returns(T, a => a))
|
||||||
)})
|
]})
|
||||||
assert.strictEqual(incremental.add(7, undefined), 7)
|
assert.strictEqual(incremental.add(7, undefined), 7)
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
incremental.resolve('add', [TypeOfTypes, Undefined]).returns,
|
incremental.resolve('add', [TypeOfTypes, Undefined]).returns,
|
||||||
|
@ -34,10 +34,10 @@ describe('TypeDispatcher', () => {
|
||||||
NumberT)
|
NumberT)
|
||||||
// Oops, changed my mind ;-), make it work like NaN with numbers:
|
// Oops, changed my mind ;-), make it work like NaN with numbers:
|
||||||
const alwaysNaN = Returns(NumberT, () => NaN)
|
const alwaysNaN = Returns(NumberT, () => NaN)
|
||||||
incremental.merge({add: onType(
|
incremental.merge({add: [
|
||||||
[Undefined, NumberT], alwaysNaN,
|
match([Undefined, NumberT], alwaysNaN),
|
||||||
[NumberT, Undefined], alwaysNaN
|
match([NumberT, Undefined], alwaysNaN)
|
||||||
)})
|
]})
|
||||||
assert(isNaN(incremental.add(undefined, -3.25)))
|
assert(isNaN(incremental.add(undefined, -3.25)))
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
incremental.add.resolve([Undefined, NumberT]).returns,
|
incremental.add.resolve([Undefined, NumberT]).returns,
|
||||||
|
@ -61,7 +61,7 @@ describe('TypeDispatcher', () => {
|
||||||
assert.strictEqual(bgn.negate(true), -1)
|
assert.strictEqual(bgn.negate(true), -1)
|
||||||
assert(bgn._behaviors.negate.has([BooleanT]))
|
assert(bgn._behaviors.negate.has([BooleanT]))
|
||||||
const deps = bgn._dependencies.negate
|
const deps = bgn._dependencies.negate
|
||||||
bgn.merge({number: onType([BooleanT], Returns(NumberT, b => b ? 2 : 0))})
|
bgn.merge({number: match([BooleanT], Returns(NumberT, b => b ? 2 : 0))})
|
||||||
assert(!bgn._behaviors.negate.has([BooleanT]))
|
assert(!bgn._behaviors.negate.has([BooleanT]))
|
||||||
assert.strictEqual(bgn.negate(true), -2)
|
assert.strictEqual(bgn.negate(true), -2)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import {
|
import {
|
||||||
Implementations, onType, isPlainObject, isPlainFunction
|
Matcher, match, isPlainObject, isPlainFunction, Implementations
|
||||||
} from '../helpers.js'
|
} from '../helpers.js'
|
||||||
import {Type, Undefined, TypeOfTypes} from '../Type.js'
|
import {Type, Undefined, TypeOfTypes} from '../Type.js'
|
||||||
import {TypePattern} from '../TypePatterns.js'
|
import {TypePattern} from '../TypePatterns.js'
|
||||||
|
|
||||||
describe('Core helpers', () => {
|
describe('Core helpers', () => {
|
||||||
it('defines what Implementations are', () => {
|
it('defines what Matchers are', () => {
|
||||||
const imps = onType(Undefined, 7, [TypeOfTypes, Undefined], -3)
|
const matcher = match([TypeOfTypes, Undefined], -3)
|
||||||
assert(imps instanceof Implementations)
|
assert(matcher instanceof Matcher)
|
||||||
assert(imps.patterns instanceof Array)
|
assert(matcher.pattern instanceof TypePattern)
|
||||||
assert(imps.patterns.every(([k]) => k instanceof TypePattern))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('detects plain objects', () => {
|
it('detects plain objects', () => {
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import {pattern, Passthru} from './TypePatterns.js'
|
import {pattern, Passthru} from './TypePatterns.js'
|
||||||
|
|
||||||
export class Implementations {
|
export class Matcher {
|
||||||
constructor(imps) {
|
constructor(spec, facOrBehave) {
|
||||||
this.patterns = []
|
this.pattern = pattern(spec)
|
||||||
this._add(imps)
|
this.does = facOrBehave
|
||||||
}
|
|
||||||
_add(imps) {
|
|
||||||
for (let i = 0; i < imps.length; ++i) {
|
|
||||||
this.patterns.push([pattern(imps[i]), imps[++i]])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
also(...imps) {
|
|
||||||
this._add(imps)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onType = (...imps) => new Implementations(imps)
|
export class Implementations {
|
||||||
|
constructor(impOrImps) {
|
||||||
|
if (Array.isArray(impOrImps)) {
|
||||||
|
this.matchers = impOrImps
|
||||||
|
} else this.matchers = [impOrImps]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const match = (spec, facOrBehave) => new Matcher(spec, facOrBehave)
|
||||||
|
|
||||||
export class ImplementationsGenerator {
|
export class ImplementationsGenerator {
|
||||||
constructor(f) {
|
constructor(f) {
|
||||||
|
@ -34,7 +34,7 @@ export class ImplementationsGenerator {
|
||||||
// collection of types (and modifying the types in one would affect them
|
// collection of types (and modifying the types in one would affect them
|
||||||
// all). Hence we do:
|
// all). Hence we do:
|
||||||
|
|
||||||
export const types = new ImplementationsGenerator(() => onType(Passthru, {}))
|
export const types = new ImplementationsGenerator(() => match(Passthru, {}))
|
||||||
|
|
||||||
export class ResolutionError extends TypeError {
|
export class ResolutionError extends TypeError {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
import {TypeOfTypes, Undefined} from '#core/Type.js'
|
import {TypeOfTypes, Undefined} from '#core/Type.js'
|
||||||
import {boolnum} from '#number/helpers.js'
|
import {boolnum} from '#number/helpers.js'
|
||||||
|
|
||||||
export const indistinguishable = onType(
|
export const indistinguishable = [
|
||||||
[Undefined, Undefined], boolnum(() => true),
|
match([Undefined, Undefined], boolnum(() => true)),
|
||||||
[TypeOfTypes, TypeOfTypes], boolnum((t, u) => t === u)
|
match([TypeOfTypes, TypeOfTypes], boolnum((t, u) => t === u))
|
||||||
)
|
]
|
||||||
|
|
|
@ -1,47 +1,47 @@
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
import {NotAType, Returns, TypeOfTypes} from '#core/Type.js'
|
import {NotAType, Returns, TypeOfTypes} from '#core/Type.js'
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {Any} from "#core/TypePatterns.js"
|
||||||
import {boolnum} from "#number/helpers.js"
|
import {boolnum} from "#number/helpers.js"
|
||||||
|
|
||||||
export const zero = onType(
|
export const zero = [
|
||||||
Any, (math, T) => {
|
match(Any, (math, T) => {
|
||||||
const z = math.zero(T)
|
const z = math.zero(T)
|
||||||
return Returns(T, () => z)
|
return Returns(T, () => z)
|
||||||
},
|
}),
|
||||||
TypeOfTypes, Returns(NotAType, t => {
|
match(TypeOfTypes, Returns(NotAType, t => {
|
||||||
if ('zero' in t) return t.zero
|
if ('zero' in t) return t.zero
|
||||||
throw new RangeError(`type '${t}' has no zero element`)
|
throw new RangeError(`type '${t}' has no zero element`)
|
||||||
})
|
}))
|
||||||
)
|
]
|
||||||
|
|
||||||
export const one = onType(
|
export const one = [
|
||||||
Any, (math, T) => {
|
match(Any, (math, T) => {
|
||||||
const unit = math.one(T)
|
const unit = math.one(T)
|
||||||
return Returns(T, () => unit)
|
return Returns(T, () => unit)
|
||||||
},
|
}),
|
||||||
TypeOfTypes, Returns(NotAType, t => {
|
match(TypeOfTypes, Returns(NotAType, t => {
|
||||||
if ('one' in t) return t.one
|
if ('one' in t) return t.one
|
||||||
throw new RangeError(
|
throw new RangeError(
|
||||||
`type '${t}' has no unit element designated as "one"`)
|
`type '${t}' has no unit element designated as "one"`)
|
||||||
})
|
}))
|
||||||
)
|
]
|
||||||
|
|
||||||
export const hasnan = onType(
|
export const hasnan = [
|
||||||
Any, (math, T) => {
|
match(Any, (math, T) => {
|
||||||
const answer = math.hasnan(T)
|
const answer = math.hasnan(T)
|
||||||
return Returns(math.typeOf(answer), () => answer)
|
return Returns(math.typeOf(answer), () => answer)
|
||||||
},
|
}),
|
||||||
TypeOfTypes, boolnum(t => 'nan' in t)
|
match(TypeOfTypes, boolnum(t => 'nan' in t))
|
||||||
)
|
]
|
||||||
|
|
||||||
export const nan = onType(
|
export const nan = [
|
||||||
Any, (math, T) => {
|
match(Any, (math, T) => {
|
||||||
const notanum = math.nan(T)
|
const notanum = math.nan(T)
|
||||||
return Returns(T, () => notanum)
|
return Returns(T, () => notanum)
|
||||||
},
|
}),
|
||||||
TypeOfTypes, Returns(NotAType, t => {
|
match(TypeOfTypes, Returns(NotAType, t => {
|
||||||
if ('nan' in t) return t.nan
|
if ('nan' in t) return t.nan
|
||||||
throw new RangeError(
|
throw new RangeError(
|
||||||
`type '${t}' has no "not a number" element`)
|
`type '${t}' has no "not a number" element`)
|
||||||
})
|
}))
|
||||||
)
|
]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Any} from '#core/TypePatterns.js'
|
import {Any} from '#core/TypePatterns.js'
|
||||||
|
|
||||||
export const square = onType(Any, (math, T) => {
|
export const square = match(Any, (math, T) => {
|
||||||
const mult = math.multiply.resolve([T, T])
|
const mult = math.multiply.resolve([T, T])
|
||||||
return Returns(mult.returns, a => mult(a, a))
|
return Returns(mult.returns, a => mult(a, a))
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {ImplementationsGenerator, onType} from '#core/helpers.js'
|
import {ImplementationsGenerator, match} from '#core/helpers.js'
|
||||||
import {Passthru} from '#core/TypePatterns.js'
|
import {Passthru} from '#core/TypePatterns.js'
|
||||||
|
|
||||||
export const config = new ImplementationsGenerator(
|
export const config = new ImplementationsGenerator(
|
||||||
() => onType(Passthru, {relTol: 1e-12, absTol: 1e-15}))
|
() => match(Passthru, {relTol: 1e-12, absTol: 1e-15}))
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {ReturnsAs} from './helpers.js'
|
import {ReturnsAs} from './helpers.js'
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Any, matched} from '#core/TypePatterns.js'
|
import {Any, matched} from '#core/TypePatterns.js'
|
||||||
import {boolnum} from '#number/helpers.js'
|
import {boolnum} from '#number/helpers.js'
|
||||||
|
|
||||||
export const equal = onType([Any, Any], (math, [T, U]) => {
|
export const equal = match([Any, Any], (math, [T, U]) => {
|
||||||
// Finding the correct signature of `indistinguishable` to use for
|
// Finding the correct signature of `indistinguishable` to use for
|
||||||
// testing (approximate) equality is tricky, because T or U might
|
// testing (approximate) equality is tricky, because T or U might
|
||||||
// need to be converted for the sake of comparison, and some types
|
// need to be converted for the sake of comparison, and some types
|
||||||
|
@ -38,7 +38,7 @@ export const equal = onType([Any, Any], (math, [T, U]) => {
|
||||||
// now that we have `equal` and `exceeds`, pretty much everything else should
|
// now that we have `equal` and `exceeds`, pretty much everything else should
|
||||||
// be easy:
|
// be easy:
|
||||||
|
|
||||||
export const compare = onType([Any, Any], (math, [T, U]) => {
|
export const compare = match([Any, Any], (math, [T, U]) => {
|
||||||
const eq = math.equal.resolve([T, U])
|
const eq = math.equal.resolve([T, U])
|
||||||
const gt = math.exceeds.resolve([T, U])
|
const gt = math.exceeds.resolve([T, U])
|
||||||
const zero = math.zero(T) // asymmetry here is unfortunate, but we have
|
const zero = math.zero(T) // asymmetry here is unfortunate, but we have
|
||||||
|
@ -70,7 +70,7 @@ export const compare = onType([Any, Any], (math, [T, U]) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export const sign = onType(Any, (math, T) => {
|
export const sign = match(Any, (math, T) => {
|
||||||
const zero = math.zero(T)
|
const zero = math.zero(T)
|
||||||
const comp = math.compare.resolve([T, T])
|
const comp = math.compare.resolve([T, T])
|
||||||
return ReturnsAs(comp, t => comp(t, zero))
|
return ReturnsAs(comp, t => comp(t, zero))
|
||||||
|
@ -81,25 +81,25 @@ export const unequal = (math, types) => {
|
||||||
return ReturnsAs(eq, (...args) => !eq(...args))
|
return ReturnsAs(eq, (...args) => !eq(...args))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const larger = onType([Any, Any], (math, [T, U]) => {
|
export const larger = match([Any, Any], (math, [T, U]) => {
|
||||||
const eq = math.equal.resolve([T, U])
|
const eq = math.equal.resolve([T, U])
|
||||||
const bigger = math.exceeds.resolve([T, U])
|
const bigger = math.exceeds.resolve([T, U])
|
||||||
return boolnum((t, u) => !eq(t, u) && bigger(t, u))(math)
|
return boolnum((t, u) => !eq(t, u) && bigger(t, u))(math)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const largerEq = onType([Any, Any], (math, [T, U]) => {
|
export const largerEq = match([Any, Any], (math, [T, U]) => {
|
||||||
const eq = math.equal.resolve([T, U])
|
const eq = math.equal.resolve([T, U])
|
||||||
const bigger = math.exceeds.resolve([T, U])
|
const bigger = math.exceeds.resolve([T, U])
|
||||||
return ReturnsAs(bigger, (t, u) => eq(t, u) || bigger(t, u))
|
return ReturnsAs(bigger, (t, u) => eq(t, u) || bigger(t, u))
|
||||||
})
|
})
|
||||||
|
|
||||||
export const smaller = onType([Any, Any], (math, [T, U]) => {
|
export const smaller = match([Any, Any], (math, [T, U]) => {
|
||||||
const eq = math.equal.resolve([T, U])
|
const eq = math.equal.resolve([T, U])
|
||||||
const bigger = math.exceeds.resolve([U, T])
|
const bigger = math.exceeds.resolve([U, T])
|
||||||
return boolnum((t, u) => !eq(t, u) && bigger(u, t))(math)
|
return boolnum((t, u) => !eq(t, u) && bigger(u, t))(math)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const smallerEq = onType([Any, Any], (math, [T, U]) => {
|
export const smallerEq = match([Any, Any], (math, [T, U]) => {
|
||||||
const eq = math.equal.resolve([T, U])
|
const eq = math.equal.resolve([T, U])
|
||||||
const bigger = math.exceeds.resolve([U, T])
|
const bigger = math.exceeds.resolve([U, T])
|
||||||
return ReturnsAs(bigger, (t, u) => eq(t, u) || bigger(u, t))
|
return ReturnsAs(bigger, (t, u) => eq(t, u) || bigger(u, t))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {ReturnsAs} from './helpers.js'
|
import {ReturnsAs} from './helpers.js'
|
||||||
import {onType, ResolutionError} from '#core/helpers.js'
|
import {ResolutionError} from '#core/helpers.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {Any} from "#core/TypePatterns.js"
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {Type} from '#core/Type.js'
|
import {Type} from '#core/Type.js'
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
import {BooleanT} from '#boolean/BooleanT.js'
|
import {BooleanT} from '#boolean/BooleanT.js'
|
||||||
|
|
||||||
export const NumberT = new Type(n => typeof n === 'number', {
|
export const NumberT = new Type(n => typeof n === 'number', {
|
||||||
from: onType(BooleanT, math => math.number.resolve([BooleanT])),
|
from: match(BooleanT, math => math.number.resolve([BooleanT])),
|
||||||
one: 1,
|
one: 1,
|
||||||
zero: 0,
|
zero: 0,
|
||||||
nan: NaN
|
nan: NaN
|
||||||
|
|
|
@ -14,9 +14,9 @@ describe('NumberT Type', () => {
|
||||||
it('can convert from BooleanT to NumberT', () => {
|
it('can convert from BooleanT to NumberT', () => {
|
||||||
const convertImps = NumberT.from
|
const convertImps = NumberT.from
|
||||||
let cnvBtoN
|
let cnvBtoN
|
||||||
for (const [pattern, convFactory] of convertImps.patterns) {
|
for (const {pattern, does} of convertImps) {
|
||||||
if (pattern.match([BooleanT])[0] === 1) {
|
if (pattern.match([BooleanT])[0] === 1) {
|
||||||
cnvBtoN = convFactory(math)
|
cnvBtoN = does(math)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {NumberT} from './NumberT.js'
|
import {NumberT} from './NumberT.js'
|
||||||
|
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
|
|
||||||
export const plain = f => onType(
|
export const plain = f => match(
|
||||||
Array(f.length).fill(NumberT), Returns(NumberT, f))
|
Array(f.length).fill(NumberT), Returns(NumberT, f))
|
||||||
|
|
||||||
// Takes a behavior returning boolean, and returns a factory
|
// Takes a behavior returning boolean, and returns a factory
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {Optional} from '#core/TypePatterns.js'
|
import {Optional} from '#core/TypePatterns.js'
|
||||||
import {boolnum} from './helpers.js'
|
import {boolnum} from './helpers.js'
|
||||||
|
@ -11,7 +11,7 @@ import {NumberT} from './NumberT.js'
|
||||||
|
|
||||||
// Notice a feature of TypedDispatcher: if you specify one tolerance, you must
|
// Notice a feature of TypedDispatcher: if you specify one tolerance, you must
|
||||||
// specify both.
|
// specify both.
|
||||||
export const indistinguishable = onType(
|
export const indistinguishable = match(
|
||||||
[NumberT, NumberT, Optional([NumberT, NumberT])],
|
[NumberT, NumberT, Optional([NumberT, NumberT])],
|
||||||
boolnum((a, b, [tolerances = [0, 0]]) => {
|
boolnum((a, b, [tolerances = [0, 0]]) => {
|
||||||
const [relTol, absTol] = tolerances
|
const [relTol, absTol] = tolerances
|
||||||
|
@ -34,4 +34,4 @@ export const indistinguishable = onType(
|
||||||
// Returns truthy if a (interpreted as completely precise) represents a
|
// Returns truthy if a (interpreted as completely precise) represents a
|
||||||
// greater value than b (interpreted as completely precise). Note that even if
|
// greater value than b (interpreted as completely precise). Note that even if
|
||||||
// so, a and b might be indistinguishable() to some tolerances.
|
// so, a and b might be indistinguishable() to some tolerances.
|
||||||
export const exceeds = onType([NumberT, NumberT], boolnum((a, b) => a > b))
|
export const exceeds = match([NumberT, NumberT], boolnum((a, b) => a > b))
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import {plain} from './helpers.js'
|
import {plain} from './helpers.js'
|
||||||
import {BooleanT} from '#boolean/BooleanT.js'
|
import {BooleanT} from '#boolean/BooleanT.js'
|
||||||
|
import {match} from '#core/helpers.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'
|
||||||
|
|
||||||
const num = f => Returns(NumberT, f)
|
const num = f => Returns(NumberT, f)
|
||||||
|
|
||||||
export const number = plain(a => a)
|
export const number = [
|
||||||
number.also(
|
plain(a => a),
|
||||||
// conversions from Boolean should be consistent with one and zero:
|
// conversions from Boolean should be consistent with one and zero:
|
||||||
BooleanT, num(p => p ? NumberT.one : NumberT.zero),
|
match(BooleanT, num(p => p ? NumberT.one : NumberT.zero)),
|
||||||
[], num(() => 0)
|
match([], num(() => 0))
|
||||||
)
|
]
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {plain, boolnum} from './helpers.js'
|
||||||
import {NumberT} from './NumberT.js'
|
import {NumberT} from './NumberT.js'
|
||||||
|
|
||||||
import {Returns} from '#core/Type.js'
|
import {Returns} from '#core/Type.js'
|
||||||
import {onType} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
|
|
||||||
export const clone = plain(a => a)
|
export const clone = plain(a => a)
|
||||||
export const isnan = onType(NumberT, boolnum(isNaN))
|
export const isnan = match(NumberT, boolnum(isNaN))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue