feat: Template types #45
src
test/tuple
@ -184,6 +184,9 @@ export default class PocomathInstance {
|
|||||||
if (type === 'any' || this._templateParam(type)) continue
|
if (type === 'any' || this._templateParam(type)) continue
|
||||||
this.installType(type, spec)
|
this.installType(type, spec)
|
||||||
}
|
}
|
||||||
|
for (const [base, info] of Object.entries(other._templateTypes)) {
|
||||||
|
this._installTemplateType(info.type, info.spec)
|
||||||
|
}
|
||||||
const migrateImps = {}
|
const migrateImps = {}
|
||||||
for (const operator in other._imps) {
|
for (const operator in other._imps) {
|
||||||
if (operator != 'typeOf') { // skip the builtin, we already have it
|
if (operator != 'typeOf') { // skip the builtin, we already have it
|
||||||
@ -380,6 +383,7 @@ export default class PocomathInstance {
|
|||||||
_joinTypes(typeA, typeB, convert) {
|
_joinTypes(typeA, typeB, convert) {
|
||||||
if (!typeA) return typeB
|
if (!typeA) return typeB
|
||||||
if (!typeB) return typeA
|
if (!typeB) return typeA
|
||||||
|
if (typeA === 'any' || typeB === 'any') return 'any'
|
||||||
if (typeA === typeB) return typeA
|
if (typeA === typeB) return typeA
|
||||||
const subber = convert ? this._priorTypes : this._subtypes
|
const subber = convert ? this._priorTypes : this._subtypes
|
||||||
if (subber[typeB].has(typeA)) return typeB
|
if (subber[typeB].has(typeA)) return typeB
|
||||||
@ -612,12 +616,13 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
return behavior.does(innerRefs)
|
return behavior.does(innerRefs)
|
||||||
}
|
}
|
||||||
this._addTFimplementation(tf_imps, signature, {uses, does: patch})
|
this._addTFimplementation(
|
||||||
|
tf_imps, signature, {uses, does: patch})
|
||||||
}
|
}
|
||||||
/* Now add the catchall signature */
|
/* Now add the catchall signature */
|
||||||
let templateCall = `<${theTemplateParam}>`
|
let templateCall = `<${theTemplateParam}>`
|
||||||
/* Relying here that the base of 'Foo<T>' is 'Foo': */
|
/* Relying here that the base of 'Foo<T>' is 'Foo': */
|
||||||
let baseSignature = substituteInSig(trimSignature, templateCall, '')
|
let baseSignature = trimSignature.replaceAll(templateCall, '')
|
||||||
/* Any remaining template params are top-level */
|
/* Any remaining template params are top-level */
|
||||||
const signature = substituteInSig(
|
const signature = substituteInSig(
|
||||||
baseSignature, theTemplateParam, 'any')
|
baseSignature, theTemplateParam, 'any')
|
||||||
@ -626,6 +631,7 @@ export default class PocomathInstance {
|
|||||||
* First, prepare the type inference data:
|
* First, prepare the type inference data:
|
||||||
*/
|
*/
|
||||||
const parTypes = trimSignature.split(',')
|
const parTypes = trimSignature.split(',')
|
||||||
|
const restParam = (parTypes[parTypes.length-1].slice(0,3) === '...')
|
||||||
const topTyper = entity => this.typeOf(entity)
|
const topTyper = entity => this.typeOf(entity)
|
||||||
const inferences = parTypes.map(
|
const inferences = parTypes.map(
|
||||||
type => generateTypeExtractor(
|
type => generateTypeExtractor(
|
||||||
@ -638,10 +644,31 @@ export default class PocomathInstance {
|
|||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
`Cannot find template parameter in ${rawSignature}`)
|
`Cannot find template parameter in ${rawSignature}`)
|
||||||
}
|
}
|
||||||
|
/* And eliminate template parameters from the dependencies */
|
||||||
|
const simplifiedUses = {}
|
||||||
|
for (const dep of behavior.uses) {
|
||||||
|
let [func, needsig] = dep.split(/[()]/)
|
||||||
|
if (needsig) {
|
||||||
|
const subsig = substituteInSig(needsig, theTemplateParam, '')
|
||||||
|
if (subsig === needsig) {
|
||||||
|
simplifiedUses[dep] = dep
|
||||||
|
} else {
|
||||||
|
simplifiedUses[dep] = func
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
simplifiedUses[dep] = dep
|
||||||
|
}
|
||||||
|
}
|
||||||
/* Now build the catchall implementation */
|
/* Now build the catchall implementation */
|
||||||
const self = this
|
const self = this
|
||||||
const patch = (refs) => (...args) => {
|
const patch = (refs) => (...args) => {
|
||||||
/* First infer the type we actually should have been called for */
|
/* We unbundle the rest arg if there is one */
|
||||||
|
const regLength = args.length - 1
|
||||||
|
if (restParam) {
|
||||||
|
const restArgs = args.pop()
|
||||||
|
args = args.concat(restArgs)
|
||||||
|
}
|
||||||
|
/* Now infer the type we actually should have been called for */
|
||||||
let i = -1
|
let i = -1
|
||||||
let j = -1
|
let j = -1
|
||||||
/* collect the arg types */
|
/* collect the arg types */
|
||||||
@ -652,10 +679,15 @@ export default class PocomathInstance {
|
|||||||
if (i < inferences.length - 1) ++i
|
if (i < inferences.length - 1) ++i
|
||||||
if (inferences[i]) {
|
if (inferences[i]) {
|
||||||
const argType = inferences[i](arg)
|
const argType = inferences[i](arg)
|
||||||
if (!argType || argType === 'any') {
|
if (!argType) {
|
||||||
throw TypeError(
|
throw TypeError(
|
||||||
`Type inference failed for argument ${j} of ${name}`)
|
`Type inference failed for argument ${j} of ${name}`)
|
||||||
}
|
}
|
||||||
|
if (argType === 'any') {
|
||||||
|
throw TypeError(
|
||||||
|
`In call to ${name}, incompatible template arguments: `
|
||||||
|
+ args.map(a => JSON.stringify(a)).join(', '))
|
||||||
|
}
|
||||||
argTypes.push(argType)
|
argTypes.push(argType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -669,7 +701,11 @@ export default class PocomathInstance {
|
|||||||
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
||||||
if (instantiateFor === 'any') {
|
if (instantiateFor === 'any') {
|
||||||
// Need a more informative error message here
|
// Need a more informative error message here
|
||||||
throw TypeError('No common type for arguments to ' + name)
|
throw TypeError(
|
||||||
|
`In call to ${name}, no type unifies arguments `
|
||||||
|
+ args.toString() + '; of types ' + argTypes.toString()
|
||||||
|
+ '; note each consecutive pair must unify to a '
|
||||||
|
+ 'supertype of at least one of them')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Generate the list of actual wanted types */
|
/* Generate the list of actual wanted types */
|
||||||
@ -681,9 +717,9 @@ export default class PocomathInstance {
|
|||||||
* by instantiateFor, and for all of instantiateFor's "prior types"
|
* by instantiateFor, and for all of instantiateFor's "prior types"
|
||||||
*/
|
*/
|
||||||
for (j = 0; j < parTypes.length; ++j) {
|
for (j = 0; j < parTypes.length; ++j) {
|
||||||
if (wantTypes[i] !== parTypes[i] && wantTypes.includes('<')) {
|
if (wantTypes[j] !== parTypes[j] && wantTypes[j].includes('<')) {
|
||||||
// actually used the param and is a template
|
// actually used the param and is a template
|
||||||
self._ensureTemplateTypes(parTypes[i], instantiateFor)
|
self._ensureTemplateTypes(parTypes[j], instantiateFor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Transform the arguments if we used any conversions: */
|
/* Transform the arguments if we used any conversions: */
|
||||||
@ -691,13 +727,23 @@ export default class PocomathInstance {
|
|||||||
i = - 1
|
i = - 1
|
||||||
for (j = 0; j < args.length; ++j) {
|
for (j = 0; j < args.length; ++j) {
|
||||||
if (i < parTypes.length - 1) ++i
|
if (i < parTypes.length - 1) ++i
|
||||||
const wantType = substituteInSig(
|
let wantType = parTypes[i]
|
||||||
parTypes[i], theTemplateParam, instantiateFor)
|
if (wantType.slice(0,3) === '...') {
|
||||||
|
wantType = wantType.slice(3)
|
||||||
|
}
|
||||||
|
wantType = substituteInSig(
|
||||||
|
wantType, theTemplateParam, instantiateFor)
|
||||||
if (wantType !== parTypes[i]) {
|
if (wantType !== parTypes[i]) {
|
||||||
args[j] = self._typed.convert(args[j], wantType)
|
args[j] = self._typed.convert(args[j], wantType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Finally reassemble the rest args if there were any */
|
||||||
|
if (restParam) {
|
||||||
|
const restArgs = args.slice(regLength)
|
||||||
|
args = args.slice(0,regLength)
|
||||||
|
args.push(restArgs)
|
||||||
|
}
|
||||||
/* Arrange that the desired instantiation will be there next
|
/* Arrange that the desired instantiation will be there next
|
||||||
* time so we don't have to go through that again for this type
|
* time so we don't have to go through that again for this type
|
||||||
*/
|
*/
|
||||||
@ -706,22 +752,32 @@ export default class PocomathInstance {
|
|||||||
self._invalidate(name)
|
self._invalidate(name)
|
||||||
// And update refs because we now know the type we're instantiating
|
// And update refs because we now know the type we're instantiating
|
||||||
// for:
|
// for:
|
||||||
for (const dep of behavior.uses) {
|
const innerRefs = {}
|
||||||
let [func, needsig] = dep.split(/[()]/)
|
for (const dep in simplifiedUses) {
|
||||||
if (needsig && self._typed.isTypedFunction(refs[dep])) {
|
const simplifiedDep = simplifiedUses[dep]
|
||||||
const subsig = substituteInSig(
|
if (dep === simplifiedDep) {
|
||||||
needsig, theTemplateParam, instantiateFor)
|
innerRefs[dep] = refs[dep]
|
||||||
if (subsig !== needsig) {
|
} else {
|
||||||
refs[dep] = self._typed.find(refs[dep], subsig)
|
let [func, needsig] = dep.split(/[()]/)
|
||||||
|
if (self._typed.isTypedFunction(refs[simplifiedDep])) {
|
||||||
|
const subsig = substituteInSig(
|
||||||
|
needsig, theTemplateParam, instantiateFor)
|
||||||
|
innerRefs[dep] = self._typed.find(
|
||||||
|
refs[simplifiedDep], subsig)
|
||||||
|
} else {
|
||||||
|
innerRefs[dep] = refs[simplifiedDep]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Finally ready to make the call.
|
// Finally ready to make the call.
|
||||||
return behavior.does(refs)(...args)
|
return behavior.does(innerRefs)(...args)
|
||||||
}
|
}
|
||||||
|
// The actual uses value needs to be a set:
|
||||||
|
const outerUses = new Set(Object.values(simplifiedUses))
|
||||||
this._addTFimplementation(
|
this._addTFimplementation(
|
||||||
tf_imps, signature, {uses: behavior.uses, does: patch})
|
tf_imps, signature, {uses: outerUses, does: patch})
|
||||||
}
|
}
|
||||||
|
this._correctPartialSelfRefs(tf_imps)
|
||||||
const tf = this._typed(name, tf_imps)
|
const tf = this._typed(name, tf_imps)
|
||||||
Object.defineProperty(this, name, {configurable: true, value: tf})
|
Object.defineProperty(this, name, {configurable: true, value: tf})
|
||||||
return tf
|
return tf
|
||||||
@ -742,9 +798,17 @@ export default class PocomathInstance {
|
|||||||
let part_self_references = []
|
let part_self_references = []
|
||||||
for (const dep of uses) {
|
for (const dep of uses) {
|
||||||
let [func, needsig] = dep.split(/[()]/)
|
let [func, needsig] = dep.split(/[()]/)
|
||||||
const needTypes = needsig ? typesOfSignature(needsig) : new Set()
|
/* Safety check that can perhaps be removed:
|
||||||
/* For now, punt on template parameters */
|
* Verify that the desired signature has been fully grounded:
|
||||||
if (needTypes.has(theTemplateParam)) needsig = ''
|
*/
|
||||||
|
if (needsig) {
|
||||||
|
const trysig = substituteInSig(needsig, theTemplateParam, '')
|
||||||
|
if (trysig !== needsig) {
|
||||||
|
throw new Error(
|
||||||
|
'Attempt to add a template implementation: ' +
|
||||||
|
`${signature} with dependency ${dep}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (func === 'self') {
|
if (func === 'self') {
|
||||||
if (needsig) {
|
if (needsig) {
|
||||||
if (full_self_referential) {
|
if (full_self_referential) {
|
||||||
@ -791,17 +855,77 @@ export default class PocomathInstance {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (part_self_references.length) {
|
if (part_self_references.length) {
|
||||||
imps[signature] = this._typed.referTo(
|
/* There is an obstruction here. The list part_self_references
|
||||||
...part_self_references, (...impls) => {
|
* might contain a signature that requires conversion for self to
|
||||||
|
* handle. But I advocated this not be allowed in typed.referTo, which
|
||||||
|
* made sense for human-written functions, but is unfortunate now.
|
||||||
|
* So we have to defer creating these and correct them later, at
|
||||||
|
* least until we can add an option to typed-function.
|
||||||
|
*/
|
||||||
|
imps[signature] = {
|
||||||
|
deferred: true,
|
||||||
|
builtRefs: refs,
|
||||||
|
sigDoes: does,
|
||||||
|
psr: part_self_references
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imps[signature] = does(refs)
|
||||||
|
}
|
||||||
|
|
||||||
|
_correctPartialSelfRefs(imps) {
|
||||||
|
for (const aSignature in imps) {
|
||||||
|
if (!(imps[aSignature].deferred)) continue
|
||||||
|
const part_self_references = imps[aSignature].psr
|
||||||
|
const corrected_self_references = []
|
||||||
|
for (const neededSig of part_self_references) {
|
||||||
|
// Have to find a match for neededSig among the other signatures
|
||||||
|
// of this function. That's a job for typed-function, but we will
|
||||||
|
// try here:
|
||||||
|
if (neededSig in imps) { // the easy case
|
||||||
|
corrected_self_references.push(neededSig)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// No exact match, have to try to get one that matches with
|
||||||
|
// subtypes since the whole conversion thing in typed-function
|
||||||
|
// is too complicated to reproduce
|
||||||
|
let foundSig = false
|
||||||
|
const typeList = typesOfSignature(neededSig)
|
||||||
|
for (const otherSig in imps) {
|
||||||
|
const otherTypeList = typesOfSignature(otherSig)
|
||||||
|
if (typeList.length !== otherTypeList.length) continue
|
||||||
|
const allMatch = true
|
||||||
|
for (let k = 0; k < typeList.length; ++k) {
|
||||||
|
if (this._subtypes[otherTypeList[k]].has(typeList[k])) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allMatch = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (allMatch) {
|
||||||
|
foundSig = otherSig
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundSig) {
|
||||||
|
corrected_self_references.push(foundSig)
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Implement inexact self-reference in typed-function for '
|
||||||
|
+ neededSig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const refs = imps[aSignature].builtRefs
|
||||||
|
const does = imps[aSignature].sigDoes
|
||||||
|
imps[aSignature] = this._typed.referTo(
|
||||||
|
...corrected_self_references, (...impls) => {
|
||||||
for (let i = 0; i < part_self_references.length; ++i) {
|
for (let i = 0; i < part_self_references.length; ++i) {
|
||||||
refs[`self(${part_self_references[i]})`] = impls[i]
|
refs[`self(${part_self_references[i]})`] = impls[i]
|
||||||
}
|
}
|
||||||
return does(refs)
|
return does(refs)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
imps[signature] = does(refs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function analyzes the template and makes sure the
|
/* This function analyzes the template and makes sure the
|
||||||
@ -817,7 +941,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
let instantiations
|
let instantiations
|
||||||
if (this._templateParam(arg)) { // 1st-level template
|
if (this._templateParam(arg)) { // 1st-level template
|
||||||
instantiations = new Set(this._priorTypes(type))
|
instantiations = new Set(this._priorTypes[type])
|
||||||
instantiations.add(type)
|
instantiations.add(type)
|
||||||
} else { // nested template
|
} else { // nested template
|
||||||
instantiations = this._ensureTemplateTypes(arg, type)
|
instantiations = this._ensureTemplateTypes(arg, type)
|
||||||
@ -825,7 +949,7 @@ export default class PocomathInstance {
|
|||||||
const resultingTypes = new Set()
|
const resultingTypes = new Set()
|
||||||
for (const iType of instantiations) {
|
for (const iType of instantiations) {
|
||||||
const resultType = this._maybeAddTemplateType(base, iType)
|
const resultType = this._maybeAddTemplateType(base, iType)
|
||||||
if (resultType) resultingTypes.push(resultType)
|
if (resultType) resultingTypes.add(resultType)
|
||||||
}
|
}
|
||||||
return resultingTypes
|
return resultingTypes
|
||||||
}
|
}
|
||||||
@ -840,7 +964,7 @@ export default class PocomathInstance {
|
|||||||
// OK, need to generate the type from the template
|
// OK, need to generate the type from the template
|
||||||
// Set up refines, before, test, and from
|
// Set up refines, before, test, and from
|
||||||
const newTypeSpec = {}
|
const newTypeSpec = {}
|
||||||
const template = this._templateTypes[base]
|
const template = this._templateTypes[base].spec
|
||||||
if (!template) {
|
if (!template) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Implementor error in _maybeAddTemplateType ${base} ${instantiator}`)
|
`Implementor error in _maybeAddTemplateType ${base} ${instantiator}`)
|
||||||
@ -848,6 +972,7 @@ export default class PocomathInstance {
|
|||||||
const instantiatorSpec = this.Types[instantiator]
|
const instantiatorSpec = this.Types[instantiator]
|
||||||
if (instantiatorSpec.refines) {
|
if (instantiatorSpec.refines) {
|
||||||
// Assuming all templates are covariant, for now
|
// Assuming all templates are covariant, for now
|
||||||
|
this._maybeAddTemplateType(base, instantiatorSpec.refines)
|
||||||
newTypeSpec.refines = `${base}<${instantiatorSpec.refines}>`
|
newTypeSpec.refines = `${base}<${instantiatorSpec.refines}>`
|
||||||
}
|
}
|
||||||
let beforeTypes = []
|
let beforeTypes = []
|
||||||
@ -867,9 +992,10 @@ export default class PocomathInstance {
|
|||||||
if (template.from) {
|
if (template.from) {
|
||||||
newTypeSpec.from = {}
|
newTypeSpec.from = {}
|
||||||
for (let source in template.from) {
|
for (let source in template.from) {
|
||||||
source = substituteInSig(source, theTemplateParam, instantiator)
|
const instSource = substituteInSig(
|
||||||
const usesFromParam = false
|
source, theTemplateParam, instantiator)
|
||||||
for (const word of source.split(/[<>]/)) {
|
let usesFromParam = false
|
||||||
|
for (const word of instSource.split(/[<>]/)) {
|
||||||
if (word === templateFromParam) {
|
if (word === templateFromParam) {
|
||||||
usesFromParam = true
|
usesFromParam = true
|
||||||
break
|
break
|
||||||
@ -878,12 +1004,12 @@ export default class PocomathInstance {
|
|||||||
if (usesFromParam) {
|
if (usesFromParam) {
|
||||||
for (const iFrom in instantiatorSpec.from) {
|
for (const iFrom in instantiatorSpec.from) {
|
||||||
const finalSource = substituteInSig(
|
const finalSource = substituteInSig(
|
||||||
source, templateFromParam, iFrom)
|
instSource, templateFromParam, iFrom)
|
||||||
newTypeSpec[finalSource] = template.from[source](
|
newTypeSpec.from[finalSource] = template.from[source](
|
||||||
instantiatorSpec.from[iFrom])
|
instantiatorSpec.from[iFrom])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newTypeSpec[source] = template.from[source]
|
newTypeSpec.from[instSource] = template.from[source]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,9 @@ export function generateTypeExtractor(
|
|||||||
if (!(base in templates)) return false // unknown template
|
if (!(base in templates)) return false // unknown template
|
||||||
const arg = type.slice(base.length+1, -1)
|
const arg = type.slice(base.length+1, -1)
|
||||||
const argExtractor = generateTypeExtractor(
|
const argExtractor = generateTypeExtractor(
|
||||||
arg, param, topTyper, typeJointer, templates)
|
arg, param, topTyper, typeJoiner, templates)
|
||||||
if (!argExtractor) return false
|
if (!argExtractor) return false
|
||||||
return templates[base].infer({
|
return templates[base].spec.infer({
|
||||||
typeOf: argExtractor,
|
typeOf: argExtractor,
|
||||||
joinTypes: typeJoiner
|
joinTypes: typeJoiner
|
||||||
})
|
})
|
||||||
|
@ -8,6 +8,7 @@ import * as tuple from './tuple/native.mjs'
|
|||||||
const tupleReady = {
|
const tupleReady = {
|
||||||
Tuple: tuple.Tuple,
|
Tuple: tuple.Tuple,
|
||||||
equal: tuple.equal,
|
equal: tuple.equal,
|
||||||
|
isZero: tuple.isZero,
|
||||||
length: tuple.length,
|
length: tuple.length,
|
||||||
tuple: tuple.tuple
|
tuple: tuple.tuple
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,11 @@ Tuple.installType('Tuple', {
|
|||||||
})
|
})
|
||||||
// Now the template type that is the primary use of this
|
// Now the template type that is the primary use of this
|
||||||
Tuple.installType('Tuple<T>', {
|
Tuple.installType('Tuple<T>', {
|
||||||
// For now we will assume that any 'Type<T>' refines 'Type', so this is
|
// We are assuming that any 'Type<T>' refines 'Type', so this is
|
||||||
// not necessary:
|
// not necessary:
|
||||||
// refines: 'Tuple',
|
// refines: 'Tuple',
|
||||||
// But we need there to be a way to determine the type of a tuple:
|
// But we need there to be a way to determine the type of a tuple:
|
||||||
infer: ({typeOf, joinTypes}) => t => {
|
infer: ({typeOf, joinTypes}) => t => joinTypes(t.elts.map(typeOf)),
|
||||||
return joinTypes(t.elts.map(typeOf))
|
|
||||||
},
|
|
||||||
// For the test, we can assume that t is already a base tuple,
|
// 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
|
// and we get the test for T as an input and we have to return
|
||||||
// the test for Tuple<T>
|
// the test for Tuple<T>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
export {Tuple} from './Types/Tuple.mjs'
|
export {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
export const isZero = {
|
export const isZero = {
|
||||||
'Tuple<T>': ({'self(T)': me}) => t => t.elts.every(isZero)
|
'Tuple<T>': ({'self(T)': me}) => t => t.elts.every(e => me(e))
|
||||||
|
// Note we can't just say `every(me)` above since every invokes its
|
||||||
|
// callback with more arguments, which then violates typed-function's
|
||||||
|
// signature for `me`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,4 +3,4 @@ export {Tuple} from './Types/Tuple.mjs'
|
|||||||
/* The purpose of the template argument is to ensure that all of the args
|
/* The purpose of the template argument is to ensure that all of the args
|
||||||
* are convertible to the same type.
|
* are convertible to the same type.
|
||||||
*/
|
*/
|
||||||
export const tuple = {'...any': () => args => ({elts: args})}
|
export const tuple = {'...T': () => args => ({elts: args})}
|
||||||
|
@ -3,7 +3,26 @@ import math from '../../src/pocomath.mjs'
|
|||||||
|
|
||||||
describe('tuple', () => {
|
describe('tuple', () => {
|
||||||
it('can be created and provide its length', () => {
|
it('can be created and provide its length', () => {
|
||||||
assert.strictEqual(math.length(math.tuple(3,5.2,2n)), 3)
|
assert.strictEqual(math.length(math.tuple(3, 5.2, 2)), 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not allow unification by converting consecutive arguments', () => {
|
||||||
|
assert.throws(() => math.tuple(3, 5.2, 2n), /TypeError.*unif/)
|
||||||
|
// Hence, the order matters in a slightly unfortunate way,
|
||||||
|
// but I think being a little ragged in these edge cases is OK:
|
||||||
|
assert.throws(
|
||||||
|
() => math.tuple(3, 2n, math.complex(5.2)),
|
||||||
|
/TypeError.*unif/)
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.tuple(3, math.complex(2n), 5.2),
|
||||||
|
{elts: [math.complex(3), math.complex(2n), math.complex(5.2)]})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can be tested for zero', () => {
|
||||||
|
assert.strictEqual(math.isZero(math.tuple(0,1)), false)
|
||||||
|
assert.strictEqual(math.isZero(math.tuple(0n,0n,0n,0n)), true)
|
||||||
|
assert.strictEqual(math.isZero(math.tuple(0,0.001,0)), false)
|
||||||
|
assert.strictEqual(math.isZero(math.tuple(0,math.complex(0,0))), true)
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user