refactor: change onType to match and take only one pattern and result (#22)
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:
Glen Whitney 2025-04-22 05:01:21 +00:00 committed by Glen Whitney
parent 491e207fad
commit 236f46c0c0
22 changed files with 147 additions and 135 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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', () => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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