From 27bf23db544d5aa74d319ab0da9a7ea3d1b442fc Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 3 Aug 2022 11:27:40 -0700 Subject: [PATCH] feat(Tuple): Stub for a template type This adds the target initial definition of a homogeneous Tuple type, and just enough processing that the type can be defined and non-template methods that deal with the generic base type Tuple can be called. Still has no actual template instantiation. --- src/core/PocomathInstance.mjs | 29 ++++++++++++- src/pocomath.mjs | 10 ++++- src/tuple/Types/Tuple.mjs | 82 +++++++++++++++++++++++++++++++++++ src/tuple/equal.mjs | 14 ++++++ src/tuple/isZero.mjs | 6 +++ src/tuple/length.mjs | 3 ++ src/tuple/native.mjs | 20 +++++++++ src/tuple/tuple.mjs | 6 +++ test/tuple/_native.mjs | 9 ++++ 9 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 src/tuple/Types/Tuple.mjs create mode 100644 src/tuple/equal.mjs create mode 100644 src/tuple/isZero.mjs create mode 100644 src/tuple/length.mjs create mode 100644 src/tuple/native.mjs create mode 100644 src/tuple/tuple.mjs create mode 100644 test/tuple/_native.mjs diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index a698488..22720d0 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -45,6 +45,8 @@ export default class PocomathInstance { */ this.Types = {any: 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 /* The following gives for each type, a set of all types that could * 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 */ installType(type, spec) { - if (this._templateParam(type)) { + const parts = type.split(/[<,>]/) + if (this._templateParam(parts[0])) { throw new SyntaxError( `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 (spec !== this.Types[type]) { throw new SyntaxError(`Conflicting definitions of type ${type}`) @@ -353,7 +360,6 @@ export default class PocomathInstance { // update the typeOf function const imp = {} imp[type] = {uses: new Set(), does: () => () => type} - console.log('Adding', type, 'to typeOf') this._installFunctions({typeOf: imp}) } @@ -393,6 +399,7 @@ export default class PocomathInstance { } return 'any' } + /* Returns a list of all types that have been mentioned in the * 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)) } + /* 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 */ _installFunctions(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` + ` signatures: ${Object.keys(imps)}`) } + // Mark this method as being in the midst of being reassembled Object.defineProperty(this, name, {configurable: true, value: 'limbo'}) const tf_imps = {} for (const [rawSignature, behavior] of usableEntries) { diff --git a/src/pocomath.mjs b/src/pocomath.mjs index dee980e..9081283 100644 --- a/src/pocomath.mjs +++ b/src/pocomath.mjs @@ -3,10 +3,18 @@ import PocomathInstance from './core/PocomathInstance.mjs' import * as numbers from './number/native.mjs' import * as bigints from './bigint/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 ops from './ops/all.mjs' const math = PocomathInstance.merge( - 'math', numbers, bigints, complex, generic, ops) + 'math', numbers, bigints, complex, tupleReady, generic, ops) export default math diff --git a/src/tuple/Types/Tuple.mjs b/src/tuple/Types/Tuple.mjs new file mode 100644 index 0000000..a6dab25 --- /dev/null +++ b/src/tuple/Types/Tuple.mjs @@ -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', { + // For now we will assume that any 'Type' 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 + 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: + from: { + 'Tuple': 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. Not sure if we really + // want that, but we'll try it just for kicks. + U: convert => u => ({elts: [convert(u)]}) + } +}) + +Tuple.promoteUnary = { + 'Tuple': ({'self(T)': me}) => t => ({elts: t.elts.map(me)}) +} + +Tuple.promoteBinaryUnary = { + 'Tuple,Tuple': ({'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,Tuple': ({'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,Tuple': ({'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} diff --git a/src/tuple/equal.mjs b/src/tuple/equal.mjs new file mode 100644 index 0000000..8186624 --- /dev/null +++ b/src/tuple/equal.mjs @@ -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 + } +} diff --git a/src/tuple/isZero.mjs b/src/tuple/isZero.mjs new file mode 100644 index 0000000..1c3d9d2 --- /dev/null +++ b/src/tuple/isZero.mjs @@ -0,0 +1,6 @@ +export {Tuple} from './Types/Tuple.mjs' + +export const isZero = { + 'Tuple': ({'self(T)': me}) => t => t.elts.every(isZero) +} + diff --git a/src/tuple/length.mjs b/src/tuple/length.mjs new file mode 100644 index 0000000..f3e8f2d --- /dev/null +++ b/src/tuple/length.mjs @@ -0,0 +1,3 @@ +export {Tuple} from './Types/Tuple.mjs' + +export const length = {Tuple: () => t => t.elts.length} diff --git a/src/tuple/native.mjs b/src/tuple/native.mjs new file mode 100644 index 0000000..fcf6f0d --- /dev/null +++ b/src/tuple/native.mjs @@ -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} diff --git a/src/tuple/tuple.mjs b/src/tuple/tuple.mjs new file mode 100644 index 0000000..a025028 --- /dev/null +++ b/src/tuple/tuple.mjs @@ -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})} diff --git a/test/tuple/_native.mjs b/test/tuple/_native.mjs new file mode 100644 index 0000000..3268cf6 --- /dev/null +++ b/test/tuple/_native.mjs @@ -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) + }) + +})