feat: add type reflection via ts-macros
This commit is contained in:
parent
f575582879
commit
a0324e5f2b
39
README.md
39
README.md
@ -28,8 +28,8 @@ were actually implementations of the operation "add" but with different
|
||||
signatures. I found that mechanism confusing.
|
||||
|
||||
You can verify that the new code compiles and generates implementation
|
||||
information by cloning the repository and then running `pnpm install` and
|
||||
`pnpm go`.
|
||||
information by cloning the repository and then running `pnpm install`,
|
||||
`npx ts-patch install`, and then `pnpm go`.
|
||||
|
||||
Outcomes of the refactor, corresponding to the motivations:
|
||||
|
||||
@ -107,3 +107,38 @@ expressing desired signatures could be devised; I'd want to wait until we had
|
||||
collected a larger number of use cases before trying to design them. (If
|
||||
this absquare case is essentially a one-off, it doesn't really matter
|
||||
if it is a bit elaborate.)
|
||||
|
||||
### Looking ahead
|
||||
|
||||
If this format is pursued, the next step would be to extract type information
|
||||
that's needed to assemble the factories into working operations by injecting
|
||||
the proper dependencies. There are two possible sources for this information:
|
||||
(1) parsing the `.d.ts` files generated by tsc during the build, or
|
||||
(2) generating strings encoding the types using the `$$typeToString` facility
|
||||
of the `ts-macros` package.
|
||||
|
||||
To help pick between the two, this version is instrumented with $$typeToString
|
||||
to record the type of all of the exported implementation objects, so that
|
||||
one can simply compare the output of `pnpm go` with the `.d.ts` files.
|
||||
|
||||
At a first look, some features relevant to the choice are:
|
||||
|
||||
A) With ts-macros (method 2), the type information it generates is
|
||||
available immediately upon importing the JavaScript files generated by
|
||||
the `tsc` build step. With method 1, we would need to insert an additional
|
||||
build step after `tsc` that parses the `.d.ts` files and produces one or more
|
||||
small JavaScript modules (or possibly JSON files) that contain the type
|
||||
information in a usable format.
|
||||
|
||||
B) On the other hand, the type specifications in the `d.ts` files appear
|
||||
to have many more type definitions resolved and expanded out for us, making
|
||||
them easier to read, parse, and use in the operations-assembly process.
|
||||
|
||||
C) With ts-macros we have a couple of additional package dependencies and an
|
||||
additional installation step (`npx ts-patch install`). For either method, we
|
||||
will have a TypeScript type parser module that we will need to write and
|
||||
maintain.
|
||||
|
||||
Given these points, on balance at the moment I would lean ever so slightly
|
||||
toward just parsing the `.d.ts` files -- it seems like less trouble overall
|
||||
despite the additional build step -- but I could totally go either way.
|
||||
|
@ -5,6 +5,7 @@
|
||||
scripts: {
|
||||
build: 'tsc && echo "{\\"type\\": \\"module\\"}" > build/package.json',
|
||||
go: 'pnpm build && pnpm start',
|
||||
prepare: 'ts-patch install -s',
|
||||
start: 'node --experimental-loader tsc-module-loader build',
|
||||
test: 'echo Error no test specified && exit 1',
|
||||
},
|
||||
@ -22,6 +23,8 @@
|
||||
},
|
||||
devDependencies: {
|
||||
'@types/node': '^22.7.4',
|
||||
'ts-macros': '^2.6.2',
|
||||
'ts-patch': '^3.2.1',
|
||||
typescript: '^5.6.2',
|
||||
'undici-types': '^6.19.8',
|
||||
},
|
||||
|
140
pnpm-lock.yaml
140
pnpm-lock.yaml
@ -15,6 +15,12 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^22.7.4
|
||||
version: 22.7.4
|
||||
ts-macros:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2(typescript@5.6.2)
|
||||
ts-patch:
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1
|
||||
typescript:
|
||||
specifier: ^5.6.2
|
||||
version: 5.6.2
|
||||
@ -27,20 +33,60 @@ packages:
|
||||
'@types/node@22.7.4':
|
||||
resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
commonjs-extension-resolution-loader@0.1.0:
|
||||
resolution: {integrity: sha512-XDCkM/cYIt1CfPs+LNX8nC2KKrzTx5AAlGLpx7A4BjWQCHR9LphDu9Iq5zXYf+PXhCkpLGBFiyiTnwmSnNxbWQ==}
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
global-prefix@3.0.0:
|
||||
resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
ini@1.3.8:
|
||||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||
|
||||
is-core-module@2.15.1:
|
||||
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
kind-of@6.0.3:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
@ -48,10 +94,33 @@ packages:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
|
||||
semver@7.6.3:
|
||||
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
ts-macros@2.6.2:
|
||||
resolution: {integrity: sha512-ZzEn268Td/efdvgFptYS2Hh4k8fEihF9P2QFqwX9OzEwAhdWq0oyhD0nUH6xh+mXklPKQiGQySS2NyW79tG5eA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x
|
||||
|
||||
ts-patch@3.2.1:
|
||||
resolution: {integrity: sha512-hlR43v+GUIUy8/ZGFP1DquEqPh7PFKQdDMTAmYt671kCCA6AkDQMoeFaFmZ7ObPLYOmpMgyKUqL1C+coFMf30w==}
|
||||
hasBin: true
|
||||
|
||||
tsc-module-loader@0.0.1:
|
||||
resolution: {integrity: sha512-3SIydFXw96jYU2imgULgIHKlUY8FnfDZlazvNmw4Umx/8qCwXsyDg0V2QOULf2Fw7zaI1Hbibh0mB8VzRZ/Ghg==}
|
||||
|
||||
@ -63,26 +132,67 @@ packages:
|
||||
undici-types@6.19.8:
|
||||
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
|
||||
|
||||
which@1.3.1:
|
||||
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@types/node@22.7.4':
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
commonjs-extension-resolution-loader@0.1.0:
|
||||
dependencies:
|
||||
resolve: 1.22.8
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
global-prefix@3.0.0:
|
||||
dependencies:
|
||||
ini: 1.3.8
|
||||
kind-of: 6.0.3
|
||||
which: 1.3.1
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
ini@1.3.8: {}
|
||||
|
||||
is-core-module@2.15.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
kind-of@6.0.3: {}
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
resolve@1.22.8:
|
||||
@ -91,8 +201,32 @@ snapshots:
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
semver@7.6.3: {}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
ts-macros@2.6.2(typescript@5.6.2):
|
||||
dependencies:
|
||||
typescript: 5.6.2
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
ts-patch@3.2.1:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
global-prefix: 3.0.0
|
||||
minimist: 1.2.8
|
||||
resolve: 1.22.8
|
||||
semver: 7.6.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
tsc-module-loader@0.0.1:
|
||||
dependencies:
|
||||
commonjs-extension-resolution-loader: 0.1.0
|
||||
@ -101,3 +235,9 @@ snapshots:
|
||||
typescript@5.6.2: {}
|
||||
|
||||
undici-types@6.19.8: {}
|
||||
|
||||
which@1.3.1:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * as type_data from './type'
|
||||
export * as arithmetic_functions from './arithmetic'
|
||||
export * as type from './type'
|
||||
export * as arithmetic from './arithmetic'
|
||||
|
@ -2,7 +2,7 @@ import {Complex} from './type.js'
|
||||
|
||||
import {implementations, commonSpecs} from '@/core/Dispatcher'
|
||||
import type {RawDependencies} from '@/core/Dispatcher'
|
||||
import {commonSignature, RealType} from '@/interfaces/type'
|
||||
import {commonSignature, RealType, $reflect} from '@/interfaces/type'
|
||||
import type {CommonSignature, CommonReturn} from '@/interfaces/type'
|
||||
|
||||
// Narrowly typed signature selectors, for the operations we need to use
|
||||
@ -10,7 +10,7 @@ import type {CommonSignature, CommonReturn} from '@/interfaces/type'
|
||||
const add = 'add' as const
|
||||
const divide = 'divide' as const
|
||||
|
||||
export default function <T>() {
|
||||
export function common<T>() {
|
||||
const baseSignature = commonSpecs<T>()
|
||||
const withComplex
|
||||
= <RD extends RawDependencies>(rd: RD) => baseSignature({
|
||||
@ -150,3 +150,5 @@ export function mixed<T>() {
|
||||
})
|
||||
.ship()
|
||||
}
|
||||
|
||||
$reflect!([common, mixed])
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {implementations, commonSpecs} from '@/core/Dispatcher'
|
||||
import {joinTypes} from '@/core/type'
|
||||
import {$reflect} from '@/interfaces/type'
|
||||
|
||||
import type {
|
||||
CommonInterface, CommonSignature, NaNType, OneType, ZeroType
|
||||
@ -50,7 +51,7 @@ export function lift<T>() {
|
||||
}).ship()
|
||||
}
|
||||
|
||||
export default function <T>() {
|
||||
export function common<T>() {
|
||||
const baseSignature = commonSpecs<T>()
|
||||
|
||||
return implementations<CommonSignature<Complex<T>>>()
|
||||
@ -69,3 +70,5 @@ export default function <T>() {
|
||||
})
|
||||
.ship()
|
||||
}
|
||||
|
||||
$reflect!([common, lift])
|
||||
|
@ -1,3 +1,5 @@
|
||||
import {$$typeToString} from 'ts-macros'
|
||||
|
||||
import {Configuration} from '@/core/Configuration'
|
||||
|
||||
/* First some type utilities: */
|
||||
@ -114,3 +116,11 @@ export type Dependency<T, Aux = T> = {
|
||||
|
||||
export type Dependencies<T, Names extends SignatureKey<T>, Aux = T> =
|
||||
Pick<CommonSignature<T, Aux>, Names>
|
||||
|
||||
// Macro for type reflection:
|
||||
export function $reflect<ImplTuple>(tup: ImplTuple) {
|
||||
+[[tup], <T>(elt: T) => {
|
||||
(elt as {reflectedType: string}).reflectedType
|
||||
= $$typeToString!<T>(true, false, true);
|
||||
}]
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * as type_data from './type'
|
||||
export * as arithmetic_functions from './arithmetic'
|
||||
export * as type from './type'
|
||||
export * as arithmetic from './arithmetic'
|
||||
|
@ -1,9 +1,10 @@
|
||||
import {implementations} from '@/core/Dispatcher'
|
||||
import {$reflect} from '@/interfaces/type'
|
||||
import type {CommonSignature} from '@/interfaces/type'
|
||||
|
||||
const conservativeSqrt = (a: number) => isNaN(a) ? NaN : Math.sqrt(a)
|
||||
|
||||
export default implementations<CommonSignature<number>>()
|
||||
export const common = implementations<CommonSignature<number>>()
|
||||
.independent({
|
||||
add: (a, b) => a + b,
|
||||
unaryMinus: a => -a,
|
||||
@ -24,3 +25,5 @@ export default implementations<CommonSignature<number>>()
|
||||
}
|
||||
}})
|
||||
.ship()
|
||||
|
||||
$reflect!([common])
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type {Complex} from '@/Complex/type'
|
||||
import {implementations} from '@/core/Dispatcher'
|
||||
import {$reflect} from '@/interfaces/type'
|
||||
import type {CommonSignature} from '@/interfaces/type'
|
||||
|
||||
export const number_type = {
|
||||
@ -22,10 +23,12 @@ declare module "@/interfaces/type" {
|
||||
}
|
||||
}
|
||||
|
||||
export default implementations<CommonSignature<number>>()
|
||||
export const common = implementations<CommonSignature<number>>()
|
||||
.independent({
|
||||
zero: a => 0,
|
||||
one: a => 1,
|
||||
nan: a => NaN,
|
||||
re: a => a
|
||||
}).ship()
|
||||
|
||||
$reflect!([common])
|
||||
|
@ -5,10 +5,12 @@
|
||||
"outDir": "./build",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"undici-types": [
|
||||
"./node_modules/undici-types/index.d.ts"
|
||||
]
|
||||
"ts-macros": ["./node_modules/ts-macros/dist/index.js"],
|
||||
"undici-types": ["./node_modules/undici-types/index.d.ts"]
|
||||
},
|
||||
"plugins": [
|
||||
{ "transform": "ts-macros" }
|
||||
],
|
||||
"rootDir": "./src",
|
||||
"target": "esnext",
|
||||
"types": ["node"]
|
||||
|
Loading…
Reference in New Issue
Block a user