From 4b28f8eac0c96be5eda85376e28cee3b0b268de7 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 10 Aug 2022 12:00:06 -0700 Subject: [PATCH] refactor: Add a stub for supplying return type information With this commit, there's a rudimentary function that attaches return-type information, so it can be supplied, and PocomathInstance then proceeds to check for it everywhere necessary (and supply it in all the internal operations it builds). But so far in all of those place where it's checked for, it's just ignored. I think the next step will be just to put the return type info as a property on the function itself. That should actually ease all of the code. --- src/core/PocomathInstance.mjs | 54 +++++++++++++++++++++++++++++------ src/core/returns.mjs | 11 +++++++ src/number/negate.mjs | 36 ++++++++++++++++++++++- 3 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 src/core/returns.mjs diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index 1add7eb..215ef74 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -1,7 +1,8 @@ /* Core of pocomath: create an instance */ import typed from 'typed-function' -import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs' import {makeChain} from './Chain.mjs' +import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs' +import {R_, isReturnAnnotated} from './returns.mjs' import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs' const anySpec = {} // fixed dummy specification of 'any' type @@ -82,7 +83,9 @@ export default class PocomathInstance { this._chainRepository = {} // place to store chainified functions this._installFunctions({ - typeOf: {ground: {uses: new Set(), does: () => () => 'any'}} + typeOf: { + ground: {uses: new Set(), does: () => R_('string', () => 'any')} + } }) this.joinTypes = this.joinTypes.bind(this) @@ -381,7 +384,7 @@ export default class PocomathInstance { } // update the typeOf function const imp = {} - imp[type] = {uses: new Set(), does: () => () => type} + imp[type] = {uses: new Set(), does: () => R_('string', () => type)} this._installFunctions({typeOf: imp}) } @@ -446,7 +449,10 @@ export default class PocomathInstance { } // update the typeOf function const imp = {} - imp[type] = {uses: new Set(['T']), does: ({T}) => () => `${base}<${T}>`} + imp[type] = { + uses: new Set(['T']), + does: ({T}) => R_('string', () => `${base}<${T}>`) + } this._installFunctions({typeOf: imp}) // Nothing else actually happens until we match a template parameter @@ -687,6 +693,9 @@ export default class PocomathInstance { } /* Now build the catchall implementation */ const self = this + /* For return-type annotation, we will have to fix this to + propagate the return type. At the moment we are just bagging + */ const patch = (refs) => (...args) => { /* We unbundle the rest arg if there is one */ const regLength = args.length - 1 @@ -806,7 +815,12 @@ export default class PocomathInstance { } } // Finally ready to make the call. - return behavior.does(innerRefs)(...args) + let returnSpec = behavior.does(innerRefs) + if (isReturnAnnotated(returnSpec)) { + // for now just drop return type information + returnSpec = returnSpec.via + } + return returnSpec(...args) } // The actual uses value needs to be a set: const outerUses = new Set(Object.values(simplifiedUses)) @@ -848,7 +862,12 @@ export default class PocomathInstance { _addTFimplementation(imps, signature, behavior) { const {uses, does} = behavior if (uses.length === 0) { - imps[signature] = does() + let returnSpec = does() + if (isReturnAnnotated(returnSpec)) { + // for now just dump the return information + returnSpec = returnSpec.via + } + imps[signature] = returnSpec return } const refs = {} @@ -908,7 +927,13 @@ export default class PocomathInstance { if (full_self_referential) { imps[signature] = this._typed.referToSelf(self => { refs.self = self - return does(refs) + let returnSpec = does(refs) + if (isReturnAnnotated(returnSpec)) { + // What are we going to do with the return type info in here? + // Anyhow, drop it for now + returnSpec = returnSpec.via + } + return returnSpec }) return } @@ -928,7 +953,12 @@ export default class PocomathInstance { } return } - imps[signature] = does(refs) + let returnSpec = does(refs) + if (isReturnAnnotated(returnSpec)) { + // drop return type for now + returnSpec = returnSpec.via + } + imps[signature] = returnSpec } _correctPartialSelfRefs(name, imps) { @@ -963,7 +993,13 @@ export default class PocomathInstance { for (let i = 0; i < part_self_references.length; ++i) { refs[`self(${part_self_references[i]})`] = impls[i] } - return does(refs) + let returnSpec = does(refs) + if (isReturnAnnotated(returnSpec)) { + // What will we do with the return type info in here? + // anyhow, drop it for now + returnSpec = returnSpec.via + } + return returnSpec } ) } diff --git a/src/core/returns.mjs b/src/core/returns.mjs new file mode 100644 index 0000000..d51e667 --- /dev/null +++ b/src/core/returns.mjs @@ -0,0 +1,11 @@ +/* Annotate a function with its return type */ +export function R_(type, fn) { + return {returns: type, via: fn} +} + +export function isReturnAnnotated(x) { + return x && typeof x === 'object' && 'returns' in x && 'via' in x +} + +export default R_ + diff --git a/src/number/negate.mjs b/src/number/negate.mjs index 82e27d0..5f937a4 100644 --- a/src/number/negate.mjs +++ b/src/number/negate.mjs @@ -1,3 +1,37 @@ +import R_ from '../core/returns.mjs' export * from './Types/number.mjs' -export const negate = {number: () => n => -n} +export const negate = { + NumInt: () => R_('NumInt', n => -n), + number: () => R_('number', n => -n) +} +/* Can we find a mechanism to avoid reiterating the definition + for each number (sub)type? Maybe something like: + + export const negate = {'T:number': ({T}) => R_(T, n => -n)} + + where 'T:number' is a new notation that means "T is a (non-strict) subtype + of number". That doesn't look too bad, but if we went down that route, we + might also need to make sure that if we ever implemented a PositiveNumber + type and a NegativeNumber type then we could say + + export const negate = { + PositiveNumber: () => R_('NegativeNumber', n => -n), + NegativeNumber: () => R_('PositiveNumber', n => -n), + 'T:number': ({T}) => R_(T, n => -n) + } + + + This just gets worse if there are also PosNumInt and NegNumInt types; + maybe Positive, Negative, and Zero are template types, so that + we could say: + + export const negate = { + 'Positive': ({'Negative': negT}) => R_(negT, n => -n), + 'Negative': ({'Positive': posT}) => R_(posT, n => -n), + T:number: ({T}) => R_(T, n => -n) + } + + But all of that is pretty speculative, for now let's just go with writing + out the different types. +*/