Compare commits

..

6 Commits
main ... rtti

24 changed files with 345 additions and 1223 deletions

1
.npmrc
View File

@ -1 +0,0 @@
shell-emulator=true

View File

@ -2,13 +2,11 @@
A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state. A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state.
To build and run the prototype, run:
Convenience scripts: ```
pnpm install
pnpm build-and-run
```
* `pnpm build` -- compile the package This will execute the code in the file `src/index.ts`. You can run another source file like `src/isolate_bug.ts` by doing the above and then executing `node build/isolate_bug.js`.
* `pnpm exec` -- run the compiled code produced by `pnpm build`
* `pnpm go` -- both of the above in sequence.
Important installation note:
after `pnpm install`, you must execute `npx ts-patch install` to activate the ts-macros compiler plugin.

View File

@ -1 +0,0 @@
{"type": "module"}

View File

@ -1,16 +1,12 @@
{ {
name: 'typocomath', name: 'typocomath',
version: '0.0.2', version: '0.0.1',
description: 'A hopeful final typescipt-pragmatic mathjs proof-of-concept', description: 'A hopeful final typescipt-pragmatic mathjs proof-of-concept',
main: 'index.ts', main: 'index.ts',
scripts: { scripts: {
'build-and-run': 'tspc -b && node build',
test: 'echo "Error: no test specified" && exit 1', test: 'echo "Error: no test specified" && exit 1',
build: 'mkdirp build && cpy etc/package.json build --flat && tsc',
start: 'node build',
go: 'pnpm clean && pnpm build && pnpm start',
clean: 'del-cli build',
}, },
packageManager: 'pnpm',
keywords: [ keywords: [
'math', 'math',
'algebra', 'algebra',
@ -22,14 +18,14 @@
type: 'git', type: 'git',
url: 'https://code.studioinfinity.org/glen/typocomath.git', url: 'https://code.studioinfinity.org/glen/typocomath.git',
}, },
dependencies: {
'reflect-metadata': '0.1.13',
'ts-patch': '^3.0.2',
'typescript-rtti': '0.9.6',
},
devDependencies: { devDependencies: {
'@types/node': '20.8.4', '@types/node': '20.5.0',
'cpy-cli': '5.0.0', 'ts-node': '10.9.1',
'del-cli': '5.1.0',
mkdirp: '3.0.1',
'source-map': '0.7.4',
'ts-macros': '2.6.0',
'ts-patch': '3.0.2',
typescript: '5.1.6', typescript: '5.1.6',
}, },
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,2 @@
import * as Complex from './native.js' export * as Complex from './native.js'
import * as complex from './arithmetic.js' export * as complex from './arithmetic.js'
export { complex }
export {Complex}

View File

@ -2,7 +2,6 @@ import {Complex} from './type.js'
import type { import type {
Dependencies, Signature, Returns, RealType, AliasOf Dependencies, Signature, Returns, RealType, AliasOf
} from '../interfaces/type.js' } from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
declare module "../interfaces/type" { declare module "../interfaces/type" {
interface Signatures<T> { interface Signatures<T> {
@ -82,7 +81,8 @@ export const sqrt =
addTR: Signature<'addReal', T>, addTR: Signature<'addReal', T>,
addRR: Signature<'add', RealType<T>>, addRR: Signature<'add', RealType<T>>,
addCR: Signature<'addReal', Complex<T>> addCR: Signature<'addReal', Complex<T>>
}): Signature<'sqrt', Complex<T>> => }):
Signature<'sqrt', Complex<T>> =>
z => { z => {
const myabs = dep.conservativeSqrt(dep.absquare(z)) const myabs = dep.conservativeSqrt(dep.absquare(z))
const r = dep.re(z) const r = dep.re(z)
@ -98,7 +98,4 @@ export const sqrt =
return dep.divideReal(num, denom) return dep.divideReal(num, denom)
} }
$reflect!([ export const conservativeSqrt = sqrt
add, addReal, unaryMinus, conj, subtract, multiply, absquare, divideReal,
reciprocal, divide, sqrt
])

View File

@ -1,13 +1,9 @@
import {Complex} from './type.js' import {Complex} from './type.js'
import type {Dependencies, Signature} from '../interfaces/type.js' import type {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const isReal = export const isReal =
<T>(dep: Dependencies<'add' | 'equal' | 'isReal', T>): <T>(dep: Dependencies<'add' | 'equal' | 'isReal', T>):
Signature<'isReal', Complex<T>> => Signature<'isReal', Complex<T>> =>
z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im))
export const isSquare = (): Signature<'isSquare', Complex<unknown>> => export const isSquare: Signature<'isSquare', Complex<any>> = z => true // FIXME: not correct for Complex<bigint> once we get there
z => true // FIXME: not correct for Complex<bigint> once we get there
$reflect!([isReal, isSquare])

View File

@ -1,8 +1,6 @@
import {Complex} from './type.js' import {Complex} from './type.js'
import {Dependencies, Signature} from '../interfaces/type.js' import {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const equal = export const equal =
<T>(dep: Dependencies<'equal', T>): Signature<'equal', Complex<T>> => <T>(dep: Dependencies<'equal', T>): Signature<'equal', Complex<T>> =>
(w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) (w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im)
$reflect!([equal])

View File

@ -2,12 +2,10 @@ import {joinTypes, typeOfDependency} from '../core/Dispatcher.js'
import type { import type {
ZeroType, OneType, NaNType, Dependencies, Signature, Returns ZeroType, OneType, NaNType, Dependencies, Signature, Returns
} from '../interfaces/type.js' } from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export type Complex<T> = { re: T; im: T; } export type Complex<T> = { re: T; im: T; }
export const Complex_type = { export const Complex_type = {
name: 'Complex', // just until we have reflection to tell us
test: <T>(dep: { testT: (z: unknown) => z is T }) => test: <T>(dep: { testT: (z: unknown) => z is T }) =>
(z: unknown): z is Complex<T> => (z: unknown): z is Complex<T> =>
typeof z === 'object' && z != null && 're' in z && 'im' in z typeof z === 'object' && z != null && 're' in z && 'im' in z
@ -63,5 +61,3 @@ export const nan =
export const re = export const re =
<T>(dep: Dependencies<'re', T>): Signature<'re', Complex<T>> => <T>(dep: Dependencies<'re', T>): Signature<'re', Complex<T>> =>
z => dep.re(z.re) z => dep.re(z.re)
$reflect!([complex, zero, one, nan, re])

View File

@ -1,3 +1,6 @@
import "reflect-metadata"
import { reflect, type CallSite } from 'typescript-rtti'
/* A Dispatcher is a collection of operations that do run-time /* A Dispatcher is a collection of operations that do run-time
* dispatch on the types of their arguments. Thus, every individual * dispatch on the types of their arguments. Thus, every individual
* method is like a typed-function (from the library by that name), * method is like a typed-function (from the library by that name),
@ -5,8 +8,6 @@
* for specific types (including their own). * for specific types (including their own).
*/ */
import {parseReflectedType, ImplementationDef} from './parseReflectedType.js'
// First helper types and functions for the Dispatcher // First helper types and functions for the Dispatcher
type TypeName = string type TypeName = string
@ -27,7 +28,6 @@ export function joinTypes(a: TypeName, b: TypeName) {
// Now types used in the Dispatcher class itself // Now types used in the Dispatcher class itself
type TypeSpecification = { type TypeSpecification = {
name: string, // just until we get reflection, then we can remove this property
before?: TypeName[], before?: TypeName[],
test: ((x: unknown) => boolean) test: ((x: unknown) => boolean)
| ((d: DependenciesType) => (x: unknown) => boolean), | ((d: DependenciesType) => (x: unknown) => boolean),
@ -41,18 +41,18 @@ type SpecificationsGroup = Record<string, SpecObject>
export class Dispatcher { export class Dispatcher {
installSpecification( installSpecification(
name: string, name: string,
defn: ImplementationDef, signature: Signature,
returns: TypeName,
dependencies: Record<string, Signature>,
behavior: Function // possible todo: constrain this type based behavior: Function // possible todo: constrain this type based
// on the signature, return type, and dependencies. Not sure if // on the signature, return type, and dependencies. Not sure if
// that's really possible, though. // that's really possible, though.
) { ) {
console.log('Pretending to install', name, 'with signatures') console.log('Pretending to install', name, signature, '=>', returns)
for (const signature of defn.fn.signatures) {
console.log(' ', signature.args, '=>', signature.returns) // @ts-ignore
} console.log(name, 'signature', reflect(signature))
if (defn.fn.aliasOf) { console.log(name, 'dependencies', reflect(dependencies))
console.log(' As an alias of', defn.fn.aliasOf)
}
//TODO: implement me //TODO: implement me
} }
installType(name: TypeName, typespec: TypeSpecification) { installType(name: TypeName, typespec: TypeSpecification) {
@ -60,25 +60,23 @@ export class Dispatcher {
//TODO: implement me //TODO: implement me
} }
constructor(collection: SpecificationsGroup) { constructor(collection: SpecificationsGroup) {
const implementations = []
for (const key in collection) { for (const key in collection) {
console.log('Working on', key) console.log('Working on', key)
for (const identifier in collection[key]) { for (const identifier in collection[key]) {
const item = collection[key][identifier] console.log('Handling', key, ':', identifier)
if (typeof item === 'function') { const parts = identifier.split('_')
implementations.push([key, identifier, item]) if (parts[parts.length - 1] === 'type') {
} else { parts.pop()
console.log('Handling type', key, ':', identifier) const name = parts.join('_')
this.installType( this.installType(
item.name, collection[key][identifier] as TypeSpecification) name, collection[key][identifier] as TypeSpecification)
} } else {
} const name = parts[0]
}
for (const trio of implementations) {
const [k, id, imp] = trio
console.log('Handling implementation', id, 'from', k)
this.installSpecification( this.installSpecification(
id, parseReflectedType(id, imp.reflectedType), imp) name, ['dunno'], 'unsure', {},
collection[key][identifier] as Function)
}
}
} }
} }
} }

View File

@ -1,279 +0,0 @@
export type FunctionDef = {
name: string,
aliasOf?: string,
signatures: Array<{
args: Array<{ name: string, type: string }>
returns: string
}>
}
export type ImplementationDef = {
fn: FunctionDef,
dependencies: Record<string, FunctionDef>
genericParameter: string | null
}
/**
* Parse a reflected type coming out of TypeScript into a structured object, for example:
*
* '<T>(dep: configDependency & { complex: ((re: number) => Complex<number>) | ((re: number, im: number) => Complex<number>); }) => (a: number) => number | Complex<number>'
*/
export function parseReflectedType(name: string, reflectedType: string): ImplementationDef {
console.log('For', name, 'parsing', reflectedType)
const [factoryArgs, fnsClause] = split(reflectedType, '=>', 2).map(trim)
const fn = parseAlias(name, fnsClause)
// extract the generic parameter like '<T>' at the start of the type
const genericParameter = factoryArgs.trim().startsWith('<')
? findBlockContents(factoryArgs, '<', '>')?.innerText || null
: null
const factoryArgsInner = findBlockContents(factoryArgs, '(', ')')
const depArg = split(factoryArgsInner.innerText, ':').map(trim)[1]
const depArgBlocks: string[] = depArg ? split(depArg, '&').map(trim) : []
const deps = depArgBlocks
.filter(depArgBlock => {
if (depArgBlock.startsWith('{') || depArgBlock === 'configDependency') {
return true
} else {
throw new SyntaxError(`Cannot parse dependency "${depArgBlock}"`)
}
})
.flatMap(parseDependencies)
const dependencies: Record<string, FunctionDef> = groupBy(deps, 'name')
return {fn, dependencies, genericParameter }
}
function parseDependencies(deps: string): FunctionDef[] {
if (deps === 'configDependency') {
return [{name: 'config', signatures: [{args: [], returns: 'Config'}]}]
}
const inner = findBlockContents(deps, '{', '}').innerText
return split(inner, ';')
.map(trim)
.filter(notEmpty)
.map(parseDependency)
}
// parse a dependency like "complex: ((re: number) => Complex<number>) | ((re: number, im: number) => Complex<number>)"
function parseDependency(dep: string): FunctionDef {
const [name, def] = split(dep, ':').map(trim)
return parseAlias(name, def)
}
// Parse a possibly aliased function
function parseAlias(name: string, alias: string): FunctionDef {
const { aliasOf, innerSignature } = parseAliasOf(alias)
return { name, signatures: parseSignatures(innerSignature), aliasOf }
}
// parse function signatures like ((re: number) => Complex<number>) | ((re: number, im: number) => Complex<number>)
// But also have to succeed on (a: number) => number | Complex<number>
// That's why we only split on an alternation bar `|` that's followed by
// a parenthesis; that way we avoid splitting a union return type. Note
// this is not necessarily foolproof, as there could be a return type that
// is a union with a complicated piece that has to be enclosed in parens;
// but so far it seems to work in practice.
function parseSignatures(sigs: string) {
return split(sigs, /[|]\s*(?=[(])/)
.map(trim)
.map(stripParenthesis)
.map(signature => {
const [argsBlock, returns] = split(signature, '=>').map(trim)
if (!returns) {
throw new SyntaxError(`Failed to find return type in '${signature}'`)
}
return {
args: parseArgs(argsBlock),
returns
}
})
}
// parse args like "(re: number, im: number)"
function parseArgs(argsBlock: string) : Array<{name: string, type: string}> {
const args = findBlockContents(argsBlock, '(', ')').innerText
return split(args, ',')
.map(trim)
.map(arg => {
const [name, type] = split(arg, ':').map(trim)
return { name, type}
})
}
// parse "AliasOf<"divide", (a: Complex<T>, b: RealType<Complex<T>>) => Complex<T>>"
function parseAliasOf(signature: string) : { innerSignature: string, aliasOf: string | undefined } {
if (!signature.startsWith('AliasOf')) {
return {
innerSignature: signature,
aliasOf: undefined
}
}
const inner = findBlockContents(signature, '<', '>').innerText.trim()
const [aliasOfWithQuotes, innerSignature] = split(inner, ',').map(trim)
return {
innerSignature,
aliasOf: aliasOfWithQuotes.substring(1, aliasOfWithQuotes.length - 1) // remove double quotes
}
}
// remove the outer parenthesis, for example "((re: number) => Complex<number>)" returns "(re: number) => Complex<number>"
function stripParenthesis(text: string) : string {
return text.startsWith('(') && text.endsWith(')')
? text.substring(1, text.length - 1)
: text
}
function findBlockContents(text: string, blockStart: string, blockEnd: string, startIndex = 0) : { start: number, end: number, innerText: string } | undefined {
let i = startIndex
while (!matchSubString(text, blockStart, i) && i < text.length) {
i++
}
if (i >= text.length) {
return undefined
}
i++
const start = i
while (!matchSubString(text, blockEnd, i) || matchSubString(text, '=>', i - 1)) {
i = skipBrackets(text, i)
i++
}
if (i >= text.length) {
return undefined
}
const end = i
return {
start,
end,
innerText: text.substring(start, end)
}
}
/**
* Given a string, generate a string source for a regexp that will match
* exactly the given string.
* Uses the fact that the only characters that need to be escaped in
* a character class are \, ], and ^
*/
function regexpQuote(s: string) {
const special = '\\]^'
let re = ''
for (const char of s) {
if (special.includes(char)) re += `\\${char}`
else re += `[${char}]`
}
return re
}
/**
* Split a string by a delimiter, but ignore all occurrences of the delimiter
* that are inside bracket pairs <> () [] {}
*/
export function split(
text: string, delimiter: string | RegExp, pieces = 0): string[] {
const delim: RegExp = typeof delimiter === 'string'
? new RegExp(regexpQuote(delimiter), 'y')
: new RegExp(delimiter.source, 'y')
const parts: string[] = []
let i = 0
let n = 1
let start = 0
while (i < text.length && (pieces === 0 || n < pieces)) {
i = skipBrackets(text, i)
delim.lastIndex = i
const result = delim.exec(text)
if (result) {
parts.push(text.substring(start, i))
n += 1
i += result[0].length
start = i
}
i++
}
parts.push(text.substring(start))
return parts
}
function skipBrackets(text: string, startIndex: number) : number {
let level = 0
let i = startIndex
do {
if (isBracketOpen(text, i)) {
level++
}
if (isBracketClose(text, i) && level > 0) {
level--
}
if (level === 0) {
break
}
i++
} while(i < text.length)
return i
}
function isBracketOpen(text: string, index: number) {
const char = text[index]
return char === '(' || char === '<' || char === '[' || char === '{'
}
function isBracketClose(text: string, index: number) {
const char = text[index]
// we need to take care of not matching the ">" of the operator "=>"
return char === ')' || (char === '>' && text[index - 1] !== '=') || char === ']' || char === '}'
}
function matchSubString(text: string, search: string, index: number) : boolean {
for (let i = 0; i < search.length; i++) {
if (text[i + index] !== search[i]) {
return false
}
}
return true
}
function trim(text: string) : string {
return text.trim()
}
function notEmpty(text: string) : boolean {
return text.length > 0
}
function groupBy<T>(items: T[], key: string) : Record<string, T> {
const obj: Record<string, T> = {}
items.forEach((item) => {
obj[item[key]] = item
})
return obj
}

View File

@ -1,9 +1,5 @@
import type {Dependencies, Signature} from '../interfaces/type.js' import type {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const square = export const square =
<T>(dep: Dependencies<'multiply', T>): Signature<'square', T> => <T>(dep: Dependencies<'multiply', T>): Signature<'square', T> =>
z => dep.multiply(z, z) z => dep.multiply(z, z)
// z => dep.fooBar(z, z) // fails as desired
// z => dep.multiply(z, 'foo') // still fails as desired
$reflect!([square])

View File

@ -1,7 +1,5 @@
import {Dependencies, Signature} from '../interfaces/type.js' import {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const unequal = export const unequal =
<T>(dep: Dependencies<'equal', T>): Signature<'unequal', T> => <T>(dep: Dependencies<'equal', T>): Signature<'unequal', T> =>
(x, y) => !dep.equal(x, y) (x, y) => !dep.equal(x, y)
$reflect!([unequal])

View File

@ -1,21 +1,91 @@
import {inspect} from 'node:util' import 'reflect-metadata'
import {Dispatcher} from './core/Dispatcher.js' import {Dispatcher} from './core/Dispatcher.js'
import * as Specifications from './all.js' import * as specifications from './all.js'
export default new Dispatcher(Specifications)
import {Complex} from './Complex/type.js' import {Complex} from './Complex/type.js'
import {absquare as absquare_complex} from './Complex/arithmetic.js' import {absquare as absquare_complex} from './Complex/arithmetic.js'
import {parseReflectedType} from './core/parseReflectedType.js'
import { CallSite, ReflectedObjectRef, reflect } from 'typescript-rtti'
import { square } from './generic/arithmetic.js'
// verify that typescript-rtti works (just as experiment)
const add = (a: number, b: number): number => a + b
console.log('reflect add')
console.log('parameterNames', reflect(add).parameterNames)
console.log('parameterTypes', reflect(add).parameterTypes.map(type => type.toString()))
console.log('returnType', reflect(add).returnType.toString())
console.log()
// output:
// reflect function add
// parameterNames [ 'a', 'b' ]
// parameterTypes [ 'class Number', 'class Number' ]
// returnType class Number
// try out a very simple case (just as experiment)
function createSquare(deps: {
multiply: (a: number, b: number) => number,
subtract: (a: number, b: number) => number
}) {
return (a: number) => deps.multiply(a, a)
}
console.log('reflect createSquare')
console.log('parameter names', reflect(createSquare).parameters.map(parameter => parameter.name))
// console.log('parameter[0]', (reflect(createSquare).parameters[0] as ReflectedFunctionParameter))
// @ts-ignore
console.log('parameterTypes[0]', (reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m)
console.log('parameterTypes[0].ref.m[0]',
// @ts-ignore
(reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0].n,
// @ts-ignore
(reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0]
)
console.log('parameterTypes[0].ref.m[0].t.m',
// @ts-ignore
(reflect(createSquare).parameterTypes[0] as ReflectedObjectRef)._ref.m[0].t.m
)
// @ts-ignore
// console.log('parameters[0]', reflect(createSquare).parameters[0])
// FIXME: where to find the information of the types of the dependencies multiply and subtract?
// Test whether we loose the type information when casting to a generic interface
// Conclusion: we keep the information, that is good.
console.log()
console.log('reflect createFunction')
type MathjsDependencies = Record<string, Function>
type MathjsCreateFunction = (deps: MathjsDependencies) => Function
const createFunction: MathjsCreateFunction = createSquare as MathjsCreateFunction
console.log('parameter names', reflect(createFunction).parameters.map(parameter => parameter.name))
// @ts-ignore
console.log('parameterTypes[0]', (reflect(createFunction).parameterTypes[0] as ReflectedObjectRef)._ref.m)
// TODO: more specific definition of Specifications
type Specifications = Record<string, Record<string, unknown>>
console.log()
console.log('CallSite')
function reflectSpecifications<T>(specifications: Specifications, callSite? : CallSite) {
console.log('specifications', reflect(callSite).parameters[0])
// @ts-ignore
console.log('specifications', reflect(callSite).parameters[0]._ref) // shows 'numbers', 'Complex, 'complex', 'generic'
// @ts-ignore
console.log('specifications', reflect(callSite).parameters[0]._ref.m
.find(item => item.n === 'generic').t.m) // shows 'square', 'unequal'
// @ts-ignore
console.log('specifications', reflect(callSite).parameters[0]._ref.m
.find(item => item.n === 'generic').t.m
.find(item => item.n === 'square').t.p) // [ { n: 'dep', t: [Function: t], b: undefined, v: null } ]
// @ts-ignore
// FIXME: now, we should be able to get the signature of the multiply dependency of the function square, but how?
}
reflectSpecifications<Specifications>(specifications);
// TODO: import all specifications (turned off for debugging purposes)
// export default new Dispatcher(Specifications)
const mockRealAdd = (a: number, b: number) => a+b const mockRealAdd = (a: number, b: number) => a+b
const mockComplexAbsquare = (z: Complex<number>) => z.re*z.re + z.im*z.im const mockComplexAbsquare = (z: Complex<number>) => z.re*z.re + z.im*z.im
const mockComplex = (re: number, im: number) => ({ re, im })
const config = {
predictable: false,
epsilon: 1e-14
}
const quatAbsquare = absquare_complex({ const quatAbsquare = absquare_complex({
add: mockRealAdd, add: mockRealAdd,
@ -24,30 +94,6 @@ const quatAbsquare = absquare_complex({
const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}}) const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}})
const typeTest: typeof myabs = 7 // check myabs is just a number const typeTest: typeof myabs = 7 // check myabs is just a number
console.log('Result is myabs=', myabs)
const sqrt = Specifications.numbers.sqrt({
config,
complex: mockComplex
})
console.log('Result of sqrt(16)=', sqrt(16))
console.log('Result of sqrt(-4)=', sqrt(-4))
console.log() console.log()
console.log('1) NUMBER SQRT') console.log('Result is', myabs)
console.log(`1.1) REFLECTED TYPE: "${Specifications.numbers.sqrt.reflectedType}"`)
console.log(
'1.2) PARSED TYPE:',
inspect(
parseReflectedType('sqrt', Specifications.numbers.sqrt.reflectedType),
{ depth: null, colors: true }))
console.log()
console.log('2) GENERIC SQUARE')
console.log(`1.1) REFLECTED TYPE: "${Specifications.generic.square.reflectedType}"`)
console.log('2.2) PARSED TYPE:', inspect(parseReflectedType('square', Specifications.generic.square.reflectedType), { depth: null, colors: true }))
console.log()
console.log('3) COMPLEX SQRT')
console.log(`1.1) REFLECTED TYPE: "${Specifications.complex.sqrt.reflectedType}"`)
console.log('3.2) PARSED TYPE:', inspect(parseReflectedType('sqrt', Specifications.complex.sqrt.reflectedType), { depth: null, colors: true }))

View File

@ -1,5 +1,3 @@
import {$$typeToString} from 'ts-macros'
/***** /*****
* Every typocomath type has some associated types; they need * Every typocomath type has some associated types; they need
* to be published in the following interface. The key is the * to be published in the following interface. The key is the
@ -15,10 +13,6 @@ import {$$typeToString} from 'ts-macros'
* but that's OK, the generic parameter doesn't hurt in those cases. * but that's OK, the generic parameter doesn't hurt in those cases.
****/ ****/
type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = {
[P in TKey]: (k: T[P])=>void
} [TKey] extends ((k: infer I)=>void) ? I : never
export interface AssociatedTypes<T> { export interface AssociatedTypes<T> {
undefined: { undefined: {
type: undefined type: undefined
@ -30,10 +24,10 @@ export interface AssociatedTypes<T> {
} }
type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined'] type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined']
type ALookup<T, Name extends AssociatedTypeNames> = ValueIntersectionByKeyUnion<{ type ALookup<T, Name extends AssociatedTypeNames> = {
[K in keyof AssociatedTypes<T>]: [K in keyof AssociatedTypes<T>]:
T extends AssociatedTypes<T>[K]['type'] ? AssociatedTypes<T>[K][Name] : unknown}, T extends AssociatedTypes<T>[K]['type'] ? AssociatedTypes<T>[K][Name] : never
keyof AssociatedTypes<T>> }[keyof AssociatedTypes<T>]
// For everything to compile, zero and one must be subtypes of T: // For everything to compile, zero and one must be subtypes of T:
export type ZeroType<T> = ALookup<T, 'zero'> & T export type ZeroType<T> = ALookup<T, 'zero'> & T
@ -75,16 +69,7 @@ export interface Signatures<T> {
type SignatureKey<T> = keyof Signatures<T> type SignatureKey<T> = keyof Signatures<T>
export type Signature<Name extends SignatureKey<T>, T> = Signatures<T>[Name] export type Signature<Name extends SignatureKey<T>, T> = Signatures<T>[Name]
export type Returns<Name extends SignatureKey<T>, T> = export type Returns<Name extends SignatureKey<T>, T> = ReturnType<Signatures<T>[Name]>
ReturnType<Signatures<T>[Name]> export type Dependencies<Name extends SignatureKey<T>, T> = {[K in Name]: Signature<K, T>}
type Deps<T> = T extends unknown ? { [K in keyof T]: T[K] } : never;
export type Dependencies<Name extends SignatureKey<T>, T> =
Deps<{[K in Name]: Signature<K, T>}>
export type AliasOf<Name extends string, T> = T & {aliasOf?: Name} export type AliasOf<Name extends string, T> = T & {aliasOf?: Name}
// For defining implementations with type reflection
export function $reflect<ImplTuple>(tup: ImplTuple) {
+[[tup], <T>(elt: T) =>
elt.reflectedType = $$typeToString!<T>(true, false, true)]
}

32
src/isolate_bug.ts Normal file
View File

@ -0,0 +1,32 @@
import 'reflect-metadata'
import { reflect } from 'typescript-rtti'
// function with inline object type argument
function mangle(deps: {
factor: number,
label: string
}) {
return (a: number) => deps.label + (deps.factor*a)
}
console.log('Mangling', mangle({factor: 2, label: 'doubled: '})(7))
let mdeptype = reflect(mangle).getParameter('deps').type
console.log("Deps type:", mdeptype)
console.log(" Readable:", mdeptype.toString())
// Above seems to work. However, if the object has a function member,
// it seems to lose the type of that member:
function bungle(deps: {
factor: (i: number) => number,
label: string
}) {
return (a: number) => deps.label + (deps.factor(2)*a)
}
let fn = (i:number) => 3*i
console.log("\nBungling", bungle({factor: fn, label:'hextupled: '})(7))
let bdeptype = reflect(bungle).getParameter('deps').type
console.log("Deps type:", bdeptype)
console.log(" Readable:", bdeptype.toString())

View File

@ -1,3 +1 @@
import * as numbers from './native.js' export * as numbers from './native.js'
export {numbers}

View File

@ -1,35 +1,25 @@
import type {configDependency} from '../core/Config.js' import type {configDependency} from '../core/Config.js'
import type {Dependencies, Signature} from '../interfaces/type.js' import type {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const add = (): Signature<'add', number> => (a, b) => a + b export const add: Signature<'add', number> = (a, b) => a + b
const unaMinus = (a: number) => -a export const unaryMinus: Signature<'unaryMinus', number> = a => -a
export const unaryMinus = (): Signature<'unaryMinus', number> => unaMinus export const conj: Signature<'conj', number> = a => a
export const conj = (): Signature<'conj', number> => a => a export const subtract: Signature<'subtract', number> = (a, b) => a - b
export const subtract = (): Signature<'subtract', number> => (a, b) => a - b export const multiply: Signature<'multiply', number> = (a, b) => a * b
export const multiply = (): Signature<'multiply', number> => (a, b) => a * b export const absquare: Signature<'absquare', number> = a => a * a
export const absquare = (): Signature<'absquare', number> => a => a * a export const reciprocal: Signature<'reciprocal', number> = a => 1 / a
export const reciprocal = (): Signature<'reciprocal', number> => a => 1 / a export const divide: Signature<'divide', number> = (a, b) => a / b
export const divide = (): Signature<'divide', number> => (a, b) => a / b
const basicSqrt = (a: number) => isNaN(a) ? NaN : Math.sqrt(a) const basicSqrt = (a: number) => isNaN(a) ? NaN : Math.sqrt(a)
export const conservativeSqrt = (): Signature<'conservativeSqrt', number> => export const conservativeSqrt: Signature<'conservativeSqrt', number> = basicSqrt
basicSqrt
export const sqrt = export const sqrt =
(dep: configDependency (dep: configDependency & Dependencies<'complex', number>):
& Dependencies<'complex', number>): Signature<'sqrt', number> => { Signature<'sqrt', number> => {
if (dep.config.predictable || !dep.complex) { if (dep.config.predictable || !dep.complex) return basicSqrt
return basicSqrt
}
return a => { return a => {
if (isNaN(a)) return NaN if (isNaN(a)) return NaN
if (a >= 0) return Math.sqrt(a) if (a >= 0) return Math.sqrt(a)
return dep.complex(0, Math.sqrt(unaMinus(a))) return dep.complex(0, Math.sqrt(unaryMinus(a)))
} }
} }
$reflect!([
add, unaryMinus, conj, subtract, multiply, absquare, reciprocal, divide,
conservativeSqrt, sqrt
])

View File

@ -1,4 +1,2 @@
export * from './type.js' export * from './type.js'
export * from './arithmetic.js' export * from './arithmetic.js'
export * from './predicate.js'
export * from './relational.js'

View File

@ -1,7 +1,4 @@
import type {Signature} from '../interfaces/type.js' import type {Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const isReal = (): Signature<'isReal', number> => (a) => true export const isReal: Signature<'isReal', number> = (a) => true
export const isSquare = (): Signature<'isSquare', number> => (a) => a >= 0 export const isSquare: Signature<'isSquare', number> = (a) => a >= 0
$reflect!([isReal, isSquare])

View File

@ -1,6 +1,5 @@
import {configDependency} from '../core/Config.js' import {configDependency} from '../core/Config.js'
import {Signature} from '../interfaces/type.js' import {Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16
@ -20,4 +19,3 @@ export const equal =
return false return false
} }
$reflect!([equal])

View File

@ -1,8 +1,6 @@
import type { Signature } from '../interfaces/type.js' import type { Signature } from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const number_type = { export const number_type = {
name: 'number', // just until we have reflection to tell us
before: ['Complex'], before: ['Complex'],
test: (n: unknown): n is number => typeof n === 'number', test: (n: unknown): n is number => typeof n === 'number',
from: { string: (s: string) => +s } from: { string: (s: string) => +s }
@ -10,7 +8,7 @@ export const number_type = {
declare module "../interfaces/type" { declare module "../interfaces/type" {
interface AssociatedTypes<T> { interface AssociatedTypes<T> {
number: { numbers: {
type: number type: number
zero: 0 zero: 0
one: 1 one: 1
@ -21,9 +19,7 @@ declare module "../interfaces/type" {
} }
// I don't like the redundancy of repeating 'zero'; any way to eliminate that? // I don't like the redundancy of repeating 'zero'; any way to eliminate that?
export const zero = (): Signature<'zero', number> => (a) => 0 export const zero: Signature<'zero', number> = (a) => 0
export const one = (): Signature<'one', number> => (a) => 1 export const one: Signature<'one', number> = (a) => 1
export const nan = (): Signature<'nan', number> => (a) => NaN export const nan: Signature<'nan', number> = (a) => NaN
export const re = (): Signature<'re', number> => (a) => a export const re: Signature<'re', number> = (a) => a
$reflect!([zero, one, nan, re])

View File

@ -1,12 +1,17 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "ES2022",
"rootDir": "./src", "rootDir": "./src",
"outDir": "./build", "outDir": "./build",
"moduleResolution": "nodenext", "esModuleInterop": true,
"plugins": [ { "allowJs": false,
"transform": "ts-macros/dist/type-resolve", "noImplicitAny": false,
"transformProgram": true "moduleResolution": "Node",
} ] "module": "commonjs",
"plugins": [
{
"transform": "typescript-rtti/dist/transformer"
}
]
} }
} }