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.
This commit is contained in:
Glen Whitney 2022-08-10 12:00:06 -07:00
parent 207ac4330b
commit 4b28f8eac0
3 changed files with 91 additions and 10 deletions

View File

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

11
src/core/returns.mjs Normal file
View File

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

View File

@ -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<T>, Negative<T>, and Zero<T> are template types, so that
we could say:
export const negate = {
'Positive<T:number>': ({'Negative<T>': negT}) => R_(negT, n => -n),
'Negative<T:number>': ({'Positive<T>': 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.
*/