feat: Template types #45
@ -45,6 +45,8 @@ export default class PocomathInstance {
|
|||||||
*/
|
*/
|
||||||
this.Types = {any: anySpec}
|
this.Types = {any: anySpec}
|
||||||
this.Types[theTemplateParam] = anySpec
|
this.Types[theTemplateParam] = anySpec
|
||||||
|
// All the template types that have been defined
|
||||||
|
this._templateTypes = {}
|
||||||
this._subtypes = {} // For each type, gives all of its (in)direct subtypes
|
this._subtypes = {} // For each type, gives all of its (in)direct subtypes
|
||||||
/* The following gives for each type, a set of all types that could
|
/* The following gives for each type, a set of all types that could
|
||||||
* match in typed-function's dispatch algorithm before the given type.
|
* match in typed-function's dispatch algorithm before the given type.
|
||||||
@ -263,10 +265,15 @@ export default class PocomathInstance {
|
|||||||
* the corresponding changes to the _typed object immediately
|
* the corresponding changes to the _typed object immediately
|
||||||
*/
|
*/
|
||||||
installType(type, spec) {
|
installType(type, spec) {
|
||||||
if (this._templateParam(type)) {
|
const parts = type.split(/[<,>]/)
|
||||||
|
if (this._templateParam(parts[0])) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
`Type name '${type}' reserved for template parameter`)
|
`Type name '${type}' reserved for template parameter`)
|
||||||
}
|
}
|
||||||
|
if (parts.some(this._templateParam.bind(this))) {
|
||||||
|
// It's a template, deal with it separately
|
||||||
|
return this._installTemplateType(type, spec)
|
||||||
|
}
|
||||||
if (type in this.Types) {
|
if (type in this.Types) {
|
||||||
if (spec !== this.Types[type]) {
|
if (spec !== this.Types[type]) {
|
||||||
throw new SyntaxError(`Conflicting definitions of type ${type}`)
|
throw new SyntaxError(`Conflicting definitions of type ${type}`)
|
||||||
@ -353,7 +360,6 @@ export default class PocomathInstance {
|
|||||||
// update the typeOf function
|
// update the typeOf function
|
||||||
const imp = {}
|
const imp = {}
|
||||||
imp[type] = {uses: new Set(), does: () => () => type}
|
imp[type] = {uses: new Set(), does: () => () => type}
|
||||||
console.log('Adding', type, 'to typeOf')
|
|
||||||
this._installFunctions({typeOf: imp})
|
this._installFunctions({typeOf: imp})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +399,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
return 'any'
|
return 'any'
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Returns a list of all types that have been mentioned in the
|
/* Returns a list of all types that have been mentioned in the
|
||||||
* signatures of operations, but which have not actually been installed:
|
* signatures of operations, but which have not actually been installed:
|
||||||
*/
|
*/
|
||||||
@ -400,6 +407,23 @@ export default class PocomathInstance {
|
|||||||
return Array.from(this._usedTypes).filter(t => !(t in this.Types))
|
return Array.from(this._usedTypes).filter(t => !(t in this.Types))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Used internally to install a template type */
|
||||||
|
_installTemplateType(type, spec) {
|
||||||
|
const base = type.split('<')[0]
|
||||||
|
/* For now, just allow a single template per base type; that
|
||||||
|
* might need to change later:
|
||||||
|
*/
|
||||||
|
if (base in this._templateTypes) {
|
||||||
|
if (spec !== this._templateTypes[base].spec) {
|
||||||
|
throw new SyntaxError(
|
||||||
|
`Conflicting definitions of template type ${type}`)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Nothing actually happens until we match a template parameter
|
||||||
|
this._templateTypes[base] = {type, spec}
|
||||||
|
}
|
||||||
|
|
||||||
/* Used internally by install, see the documentation there */
|
/* Used internally by install, see the documentation there */
|
||||||
_installFunctions(functions) {
|
_installFunctions(functions) {
|
||||||
for (const [name, spec] of Object.entries(functions)) {
|
for (const [name, spec] of Object.entries(functions)) {
|
||||||
@ -509,6 +533,7 @@ export default class PocomathInstance {
|
|||||||
`Every implementation for ${name} uses an undefined type;\n`
|
`Every implementation for ${name} uses an undefined type;\n`
|
||||||
+ ` signatures: ${Object.keys(imps)}`)
|
+ ` signatures: ${Object.keys(imps)}`)
|
||||||
}
|
}
|
||||||
|
// Mark this method as being in the midst of being reassembled
|
||||||
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
||||||
const tf_imps = {}
|
const tf_imps = {}
|
||||||
for (const [rawSignature, behavior] of usableEntries) {
|
for (const [rawSignature, behavior] of usableEntries) {
|
||||||
|
@ -3,10 +3,18 @@ import PocomathInstance from './core/PocomathInstance.mjs'
|
|||||||
import * as numbers from './number/native.mjs'
|
import * as numbers from './number/native.mjs'
|
||||||
import * as bigints from './bigint/native.mjs'
|
import * as bigints from './bigint/native.mjs'
|
||||||
import * as complex from './complex/native.mjs'
|
import * as complex from './complex/native.mjs'
|
||||||
|
import * as tuple from './tuple/native.mjs'
|
||||||
|
// Most of tuple is not ready yet:
|
||||||
|
const tupleReady = {
|
||||||
|
Tuple: tuple.Tuple,
|
||||||
|
equal: tuple.equal,
|
||||||
|
length: tuple.length,
|
||||||
|
tuple: tuple.tuple
|
||||||
|
}
|
||||||
import * as generic from './generic/all.mjs'
|
import * as generic from './generic/all.mjs'
|
||||||
import * as ops from './ops/all.mjs'
|
import * as ops from './ops/all.mjs'
|
||||||
|
|
||||||
const math = PocomathInstance.merge(
|
const math = PocomathInstance.merge(
|
||||||
'math', numbers, bigints, complex, generic, ops)
|
'math', numbers, bigints, complex, tupleReady, generic, ops)
|
||||||
|
|
||||||
export default math
|
export default math
|
||||||
|
82
src/tuple/Types/Tuple.mjs
Normal file
82
src/tuple/Types/Tuple.mjs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/* A template type representing a homeogeneous tuple of elements */
|
||||||
|
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||||
|
|
||||||
|
const Tuple = new PocomathInstance('Tuple')
|
||||||
|
// First a base type that will generally not be used directly
|
||||||
|
Tuple.installType('Tuple', {
|
||||||
|
test: t => t && typeof t === 'object' && 'elts' in t && Array.isArray(t.elts)
|
||||||
|
})
|
||||||
|
// Now the template type that is the primary use of this
|
||||||
|
Tuple.installType('Tuple<T>', {
|
||||||
|
// For now we will assume that any 'Type<T>' refines 'Type', so this is
|
||||||
|
// not necessary:
|
||||||
|
// refines: 'Tuple',
|
||||||
|
// But we need there to be a way to determine the type of a tuple:
|
||||||
|
infer: ({typeOf, joinTypes}) => t => {
|
||||||
|
return joinTypes(t.elts.map(typeOf))
|
||||||
|
},
|
||||||
|
// For the test, we can assume that t is already a base tuple,
|
||||||
|
// and we get the test for T as an input and we have to return
|
||||||
|
// the test for Tuple<T>
|
||||||
|
test: testT => t => t.elts.every(testT),
|
||||||
|
// These are only invoked for types U such that there is already
|
||||||
|
// a conversion from U to T, and that conversion is passed as an input
|
||||||
|
// and we have to return the conversion to Tuple<T>:
|
||||||
|
from: {
|
||||||
|
'Tuple<U>': convert => tu => ({elts: tu.elts.map(convert)}),
|
||||||
|
// Here since there is no U it's a straight conversion:
|
||||||
|
T: t => ({elts: [u]}), // singleton promotion
|
||||||
|
// Whereas the following will let you go directly from an element
|
||||||
|
// convertible to T to a singleton Tuple<T>. Not sure if we really
|
||||||
|
// want that, but we'll try it just for kicks.
|
||||||
|
U: convert => u => ({elts: [convert(u)]})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Tuple.promoteUnary = {
|
||||||
|
'Tuple<T>': ({'self(T)': me}) => t => ({elts: t.elts.map(me)})
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple.promoteBinaryUnary = {
|
||||||
|
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB, 'self(T)': meU}) => (s,t) => {
|
||||||
|
let i = -1
|
||||||
|
let result = []
|
||||||
|
while (true) {
|
||||||
|
i += 1
|
||||||
|
if (i < s.elts.length) {
|
||||||
|
if (i < t.elts.length) result.append(meB(s.elts[i], t.elts[i]))
|
||||||
|
else results.append(meU(s.elts[i]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (i < t.elts.length) result.append(meU(t.elts[i]))
|
||||||
|
else break
|
||||||
|
}
|
||||||
|
return {elts: result}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple.promoteBinary = {
|
||||||
|
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB}) => (s,t) => {
|
||||||
|
const lim = Math.max(s.elts.length, t.elts.length)
|
||||||
|
const result = []
|
||||||
|
for (let i = 0; i < lim; ++i) {
|
||||||
|
result.append(meB(s.elts[i], t.elts[i]))
|
||||||
|
}
|
||||||
|
return {elts: result}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple.promoteBinaryStrict = {
|
||||||
|
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB}) => (s,t) => {
|
||||||
|
if (s.elts.length !== t.elts.length) {
|
||||||
|
throw new RangeError('Tuple length mismatch') // get name of self ??
|
||||||
|
}
|
||||||
|
const result = []
|
||||||
|
for (let i = 0; i < s.elts.length; ++i) {
|
||||||
|
result.append(meB(s.elts[i], t.elts[i]))
|
||||||
|
}
|
||||||
|
return {elts: result}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Tuple}
|
14
src/tuple/equal.mjs
Normal file
14
src/tuple/equal.mjs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export * from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
export const equal = {
|
||||||
|
// Change this to a template implementation (or maybe add template
|
||||||
|
// implementation to handle matching types, and have mixed template-base
|
||||||
|
// method returning false to catch test of two tuples of different types.
|
||||||
|
'Tuple,Tuple': ({self, length}) => (s,t) => {
|
||||||
|
if (length(s) !== length(t)) return false
|
||||||
|
for (let i = 0; i < length(s); ++i) {
|
||||||
|
if (!self(s.elts[i], t.elts[i])) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
6
src/tuple/isZero.mjs
Normal file
6
src/tuple/isZero.mjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
export const isZero = {
|
||||||
|
'Tuple<T>': ({'self(T)': me}) => t => t.elts.every(isZero)
|
||||||
|
}
|
||||||
|
|
3
src/tuple/length.mjs
Normal file
3
src/tuple/length.mjs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
export const length = {Tuple: () => t => t.elts.length}
|
20
src/tuple/native.mjs
Normal file
20
src/tuple/native.mjs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
export const add = Tuple.promoteBinaryUnary
|
||||||
|
export const complex = Tuple.promoteBinaryStrict
|
||||||
|
export const conjugate = Tuple.promoteUnary
|
||||||
|
// May want to replace equal with compare based on lexicographic ordering?
|
||||||
|
export {equal} from './equal.mjs'
|
||||||
|
export const invert = Tuple.promoteUnary
|
||||||
|
export {isZero} from './isZero.mjs'
|
||||||
|
export {length} from './length.mjs'
|
||||||
|
export const multiply = Tuple.promoteBinaryUnary
|
||||||
|
export const negate = Tuple.promoteUnary
|
||||||
|
export const one = Tuple.promoteUnary
|
||||||
|
export const quotient = Tuple.promoteBinaryStrict
|
||||||
|
export const roundquotient = Tuple.promoteBinaryStrict
|
||||||
|
export const sqrt = Tuple.promoteUnary
|
||||||
|
export {tuple} from './tuple.mjs'
|
||||||
|
export const zero = Tuple.promoteUnary
|
||||||
|
|
||||||
|
export {Tuple}
|
6
src/tuple/tuple.mjs
Normal file
6
src/tuple/tuple.mjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
/* The purpose of the template argument is to ensure that all of the args
|
||||||
|
* are convertible to the same type.
|
||||||
|
*/
|
||||||
|
export const tuple = {'...any': () => args => ({elts: args})}
|
9
test/tuple/_native.mjs
Normal file
9
test/tuple/_native.mjs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import assert from 'assert'
|
||||||
|
import math from '../../src/pocomath.mjs'
|
||||||
|
|
||||||
|
describe('tuple', () => {
|
||||||
|
it('can be created and provide its length', () => {
|
||||||
|
assert.strictEqual(math.length(math.tuple(3,5.2,2n)), 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user