feat: Narrow tsc typing of operation dependencies/implementations
This commit is contained in:
parent
90b66dc863
commit
f575582879
115
.gitignore
vendored
115
.gitignore
vendored
@ -2,12 +2,11 @@
|
|||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor backup files
|
||||||
|
*~
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
@ -17,116 +16,12 @@ pids
|
|||||||
*.seed
|
*.seed
|
||||||
*.pid.lock
|
*.pid.lock
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
# Compiled code
|
||||||
lib-cov
|
build
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
# Dependency directories
|
||||||
node_modules/
|
node_modules/
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
# TypeScript cache
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
.temp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
|
108
README.md
108
README.md
@ -1,3 +1,109 @@
|
|||||||
# math5
|
# math5
|
||||||
|
|
||||||
Yet another math core prototype for a possible future of mathjs
|
Yet another math core prototype for a possible future of mathjs.
|
||||||
|
|
||||||
|
This project is a revision of the
|
||||||
|
[typocomath](https://code.studioinfinity.org/glen/typocomath) prototype
|
||||||
|
(which was the fourth in the series picomath, pocomath, typocomath, hence
|
||||||
|
the current name math5), preparing for an initial implementation of the
|
||||||
|
Dispatcher engine to assemble and run the methods specified in TypeScript.
|
||||||
|
|
||||||
|
Motivations for the refactor:
|
||||||
|
|
||||||
|
1. I observed that the `.d.ts` files that were being generated as a result
|
||||||
|
of the TypeScript compilation step did not contain sufficient type information
|
||||||
|
to see what each implementation/factory for each operation of the resulting
|
||||||
|
math module would do. This lack suggested that the TypeScript definitions of
|
||||||
|
the implementations and factories were not actually being fully typechecked.
|
||||||
|
|
||||||
|
2. I felt that there was still a significant amount of redundancy in the
|
||||||
|
implementation files. For example, in typocomath/src/numbers/arithmetic, it
|
||||||
|
reiterates for every arithmetic operation "foo" that "foo" implements the
|
||||||
|
number signature for the "foo" operation. It seemed like it would be
|
||||||
|
preferable to specify that this module is for the "number" type fewer times,
|
||||||
|
and not have to mention the operation name for each operation twice.
|
||||||
|
|
||||||
|
3. I did not love the creation of aliased operation names like "addReal" that
|
||||||
|
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`.
|
||||||
|
|
||||||
|
Outcomes of the refactor, corresponding to the motivations:
|
||||||
|
|
||||||
|
1a. You can browse the generated `build/**/*.d.ts` files to see that they
|
||||||
|
now contain full, detailed type information on every implementation and
|
||||||
|
factory, including the exact types of the dependencies.
|
||||||
|
|
||||||
|
1b. The TypeScript compiler now correctly detected (which it had not done in
|
||||||
|
typocomath) that the intermediate real square roots in the complex `sqrt`
|
||||||
|
implementation might be used even though they had come out to `NaN`. This
|
||||||
|
outcome is direct evidence that the TypeScript compiler is now type-checking
|
||||||
|
more strictly, so we are getting greater value from using TypeScript to
|
||||||
|
specify the operations' behavior. (In addition, it led to adding the `isnan`
|
||||||
|
predicate so that the code would compile.)
|
||||||
|
|
||||||
|
2. There is less repeated information. For example,
|
||||||
|
math5/src/numbers/arithmetic only mentions `number` twice, and only mentions
|
||||||
|
each operation once.
|
||||||
|
|
||||||
|
3. Implementations/factories are now only exported under their actual
|
||||||
|
operation names, just with different signatures specified. The default name
|
||||||
|
of a dependency is the name of the operation, but when you have dependencies
|
||||||
|
on a given operation with different signatures, you can name the dependency
|
||||||
|
arbitrarily and then specify which operation it is an instance of.
|
||||||
|
|
||||||
|
Other potential advantages of the refactor:
|
||||||
|
|
||||||
|
* Assembling the implementation specifications (the main task of which
|
||||||
|
is resolving and injecting dependencies) into a running math engine could
|
||||||
|
potentially work by parsing the `.d.ts` files as generated; we would not
|
||||||
|
necessarily need to instrument the typescript code with macros to generate
|
||||||
|
the additional information needed to correctly assemble the factories.
|
||||||
|
|
||||||
|
Some disadvantages of the refactor:
|
||||||
|
|
||||||
|
* The presentation of the code is slightly more verbose in places. The
|
||||||
|
primary cause of this is the switch to a "builder" interface for collecting
|
||||||
|
implementations/factories, as advised by TypeScript guru
|
||||||
|
[jcalz](https://stackoverflow.com/questions/79025259) in order to get
|
||||||
|
narrower type inference as desired. So for example in
|
||||||
|
src/Complex/arithmetic, every factory (a "dependent implementation", as
|
||||||
|
opposed to an "independent" one that has no dependencies) is wrapped in
|
||||||
|
its own call to `dependent(dependencySpecifiers, factories)`. And that
|
||||||
|
whole chain of `dependent` calls has to be kicked off with a call to
|
||||||
|
`implementations()` and wrapped up with a call to `ship()`. Of course, the
|
||||||
|
names of those functions could be changed, but it appears that currently
|
||||||
|
there is no way to avoid these wrappers if we want TypeScript to do narrow
|
||||||
|
type inference/typechecking.
|
||||||
|
|
||||||
|
* When one module is providing multiple implementations for the same
|
||||||
|
operation, but with different signatures, it must export multiple of these
|
||||||
|
bundles of implementations generated with an `implementation(). ... .ship()`
|
||||||
|
seequence, because each bundle can only contain an operation once. The names
|
||||||
|
of these bundles are arbitrary. I think this artificial division is a little
|
||||||
|
cumbersome/confusing. See src/Complex/arithmetic for an example, in which
|
||||||
|
there is a default export with the "common" signatures for operations, and a
|
||||||
|
`mixed` export with variants for `add` and `divide` that operate on a
|
||||||
|
Complex and an ordinary number.
|
||||||
|
|
||||||
|
* The notation for the desired signatures for dependencies can still be
|
||||||
|
a bit arcane/cumbersome. It's very simple when the desired dependency
|
||||||
|
consists of the common signature for that operation. But for more unusual
|
||||||
|
situations, it can become intricate. For example, in src/Complex/arithmetic,
|
||||||
|
the `absquare` (absolute value squared, an operation needed to define
|
||||||
|
division and square root) factory needs as a dependency the addition
|
||||||
|
operation on the return type of the `absquare` operation on the base type
|
||||||
|
of the Complex number. This has ended up being specified as:
|
||||||
|
|
||||||
|
```
|
||||||
|
add: {sig: commonSignature<'add', CommonReturn<'absquare', T>>()}
|
||||||
|
```
|
||||||
|
|
||||||
|
which is a bit of a mouthful. It's possible that better utilities for
|
||||||
|
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.)
|
||||||
|
31
package.json5
Normal file
31
package.json5
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
name: 'math5',
|
||||||
|
version: '0.0.1',
|
||||||
|
description: 'Another prototype for a math core',
|
||||||
|
scripts: {
|
||||||
|
build: 'tsc && echo "{\\"type\\": \\"module\\"}" > build/package.json',
|
||||||
|
go: 'pnpm build && pnpm start',
|
||||||
|
start: 'node --experimental-loader tsc-module-loader build',
|
||||||
|
test: 'echo Error no test specified && exit 1',
|
||||||
|
},
|
||||||
|
packageManager: 'pnpm@9',
|
||||||
|
keywords: [
|
||||||
|
'math',
|
||||||
|
'algebra',
|
||||||
|
'typescript',
|
||||||
|
],
|
||||||
|
author: 'Glen Whitney',
|
||||||
|
license: 'Apache 2.0',
|
||||||
|
repository: {
|
||||||
|
type: 'git',
|
||||||
|
url: 'https://code.studioinfinity.org/glen/math5.git',
|
||||||
|
},
|
||||||
|
devDependencies: {
|
||||||
|
'@types/node': '^22.7.4',
|
||||||
|
typescript: '^5.6.2',
|
||||||
|
'undici-types': '^6.19.8',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
'tsc-module-loader': '^0.0.1',
|
||||||
|
},
|
||||||
|
}
|
103
pnpm-lock.yaml
Normal file
103
pnpm-lock.yaml
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
tsc-module-loader:
|
||||||
|
specifier: ^0.0.1
|
||||||
|
version: 0.0.1
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.7.4
|
||||||
|
version: 22.7.4
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.6.2
|
||||||
|
version: 5.6.2
|
||||||
|
undici-types:
|
||||||
|
specifier: ^6.19.8
|
||||||
|
version: 6.19.8
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@types/node@22.7.4':
|
||||||
|
resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==}
|
||||||
|
|
||||||
|
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==}
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-core-module@2.15.1:
|
||||||
|
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
path-parse@1.0.7:
|
||||||
|
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||||
|
|
||||||
|
resolve@1.22.8:
|
||||||
|
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
supports-preserve-symlinks-flag@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
tsc-module-loader@0.0.1:
|
||||||
|
resolution: {integrity: sha512-3SIydFXw96jYU2imgULgIHKlUY8FnfDZlazvNmw4Umx/8qCwXsyDg0V2QOULf2Fw7zaI1Hbibh0mB8VzRZ/Ghg==}
|
||||||
|
|
||||||
|
typescript@5.6.2:
|
||||||
|
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
undici-types@6.19.8:
|
||||||
|
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@types/node@22.7.4':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.19.8
|
||||||
|
|
||||||
|
commonjs-extension-resolution-loader@0.1.0:
|
||||||
|
dependencies:
|
||||||
|
resolve: 1.22.8
|
||||||
|
|
||||||
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
is-core-module@2.15.1:
|
||||||
|
dependencies:
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
path-parse@1.0.7: {}
|
||||||
|
|
||||||
|
resolve@1.22.8:
|
||||||
|
dependencies:
|
||||||
|
is-core-module: 2.15.1
|
||||||
|
path-parse: 1.0.7
|
||||||
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
|
supports-preserve-symlinks-flag@1.0.0: {}
|
||||||
|
|
||||||
|
tsc-module-loader@0.0.1:
|
||||||
|
dependencies:
|
||||||
|
commonjs-extension-resolution-loader: 0.1.0
|
||||||
|
resolve: 1.22.8
|
||||||
|
|
||||||
|
typescript@5.6.2: {}
|
||||||
|
|
||||||
|
undici-types@6.19.8: {}
|
2
src/Complex/all.ts
Normal file
2
src/Complex/all.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * as type_data from './type'
|
||||||
|
export * as arithmetic_functions from './arithmetic'
|
152
src/Complex/arithmetic.ts
Normal file
152
src/Complex/arithmetic.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import {Complex} from './type.js'
|
||||||
|
|
||||||
|
import {implementations, commonSpecs} from '@/core/Dispatcher'
|
||||||
|
import type {RawDependencies} from '@/core/Dispatcher'
|
||||||
|
import {commonSignature, RealType} from '@/interfaces/type'
|
||||||
|
import type {CommonSignature, CommonReturn} from '@/interfaces/type'
|
||||||
|
|
||||||
|
// Narrowly typed signature selectors, for the operations we need to use
|
||||||
|
// with atypical signatures:
|
||||||
|
const add = 'add' as const
|
||||||
|
const divide = 'divide' as const
|
||||||
|
|
||||||
|
export default function <T>() {
|
||||||
|
const baseSignature = commonSpecs<T>()
|
||||||
|
const withComplex
|
||||||
|
= <RD extends RawDependencies>(rd: RD) => baseSignature({
|
||||||
|
...rd, complex: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const realSignature = commonSpecs<RealType<T>>()
|
||||||
|
|
||||||
|
return implementations<CommonSignature<Complex<T>>>()
|
||||||
|
.dependent(withComplex({add: {}}), {
|
||||||
|
add: dep => (w, z) =>
|
||||||
|
dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im))
|
||||||
|
})
|
||||||
|
.dependent(withComplex({unaryMinus: {}}), {
|
||||||
|
unaryMinus: dep => z =>
|
||||||
|
dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im))
|
||||||
|
})
|
||||||
|
.dependent(withComplex({unaryMinus: {}, conj: {}}), {
|
||||||
|
conj: dep => z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im))
|
||||||
|
})
|
||||||
|
.dependent(withComplex({subtract: {}}), {
|
||||||
|
subtract: dep => (w, z) =>
|
||||||
|
dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.re, z.re))
|
||||||
|
})
|
||||||
|
.dependent(withComplex({add: {}, subtract: {}, multiply: {}, conj: {}}), {
|
||||||
|
multiply: dep => (w, z) => {
|
||||||
|
const mult = dep.multiply
|
||||||
|
const realpart = dep.subtract(
|
||||||
|
mult( w.re, z.re), mult(dep.conj(w.im), z.im))
|
||||||
|
const imagpart = dep.add(
|
||||||
|
mult(dep.conj(w.re), z.im), mult( w.im, z.re))
|
||||||
|
return dep.complex(realpart, imagpart)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.dependent(baseSignature({
|
||||||
|
absquare: {},
|
||||||
|
add: {sig: commonSignature<'add', CommonReturn<'absquare', T>>()}
|
||||||
|
}), {
|
||||||
|
absquare: dep => z => dep.add(dep.absquare(z.re), dep.absquare(z.im))
|
||||||
|
})
|
||||||
|
.dependent({
|
||||||
|
conj: {},
|
||||||
|
absquare: {},
|
||||||
|
divideReal: {
|
||||||
|
is: divide,
|
||||||
|
sig: (a: Complex<T>, b: RealType<T>) => ({} as Complex<T>)
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
reciprocal: dep => z => dep.divideReal(dep.conj(z), dep.absquare(z))
|
||||||
|
})
|
||||||
|
.dependent({multiply: {}, reciprocal: {}}, {
|
||||||
|
divide: dep => (w,z) => dep.multiply(w, dep.reciprocal(z))
|
||||||
|
})
|
||||||
|
// The dependencies are tricky in the implementation of `sqrt` below,
|
||||||
|
// because there are three types involved: Complex<T>, T, and
|
||||||
|
// RealType<T>, all of which might be different.
|
||||||
|
// We have to get it straight which operations we need on each type;
|
||||||
|
// for example, we need `add` on three different combinations:
|
||||||
|
.dependent({
|
||||||
|
absquare: {}, re: {}, // Complex<T>-dependencies
|
||||||
|
...withComplex({zero: {}}), // T-dependencies
|
||||||
|
// And RealType<T>-dependencies:
|
||||||
|
...realSignature({
|
||||||
|
conservativeSqrt: {}, equal: {}, unaryMinus: {}, isnan: {}
|
||||||
|
}),
|
||||||
|
// And now mixed dependencies:
|
||||||
|
divideReal: {
|
||||||
|
is: divide,
|
||||||
|
sig: (a: Complex<T>, b: RealType<T>) => ({} as Complex<T>)
|
||||||
|
},
|
||||||
|
addRR: {
|
||||||
|
is: add,
|
||||||
|
sig: (a: RealType<T>, b: RealType<T>) => ({} as RealType<T>)
|
||||||
|
},
|
||||||
|
addTR: {is: add, sig: (a: T, b: RealType<T>) => ({} as T)},
|
||||||
|
addCR: {
|
||||||
|
is: add,
|
||||||
|
sig: (a: Complex<T>, b: RealType<T>) => ({} as Complex<T>)
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
sqrt: dep => z => {
|
||||||
|
const absq = dep.absquare(z)
|
||||||
|
const myabs = dep.conservativeSqrt(absq)
|
||||||
|
if (dep.isnan(myabs)) {
|
||||||
|
throw new RangeError(
|
||||||
|
`sqrt(${z}): cannot take square root of norm square ${absq}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const r = dep.re(z)
|
||||||
|
const negr = dep.unaryMinus(r)
|
||||||
|
if (dep.equal(myabs, negr)) {
|
||||||
|
// pure imaginary square root; z.im already zero
|
||||||
|
const rootNegr = dep.conservativeSqrt(negr)
|
||||||
|
if (dep.isnan(rootNegr)) {
|
||||||
|
throw new RangeError(
|
||||||
|
`sqrt(${z}): cannot take square root of `
|
||||||
|
+ `negative real part ${negr}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return dep.complex(
|
||||||
|
dep.zero(z.re), dep.addTR(z.im, rootNegr))
|
||||||
|
}
|
||||||
|
const num = dep.addCR(z, myabs)
|
||||||
|
const denomsq = dep.addRR(dep.addRR(myabs, myabs), dep.addRR(r, r))
|
||||||
|
const denom = dep.conservativeSqrt(denomsq)
|
||||||
|
if (dep.isnan(denom)) {
|
||||||
|
throw new RangeError(
|
||||||
|
`sqrt(z) for z = ${z}: cannot take square root of `
|
||||||
|
+ `2|z| + 2re(z) = ${denomsq}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return dep.divideReal(num, denom)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ship()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional implementations for non-uniform signatures
|
||||||
|
export function mixed<T>() {
|
||||||
|
return implementations<{
|
||||||
|
add: (z: Complex<T>, r: RealType<T>) => Complex<T>,
|
||||||
|
divide: (z: Complex<T>, r: RealType<T>) => Complex<T>,
|
||||||
|
complex: CommonSignature<T>['complex']
|
||||||
|
}>()
|
||||||
|
.dependent({
|
||||||
|
addTR: {is: add, sig: (a: T, b: RealType<T>) => ({} as T)},
|
||||||
|
complex: {}
|
||||||
|
}, {
|
||||||
|
add: dep => (z, r) => dep.complex(dep.addTR(z.re, r), z.im)
|
||||||
|
})
|
||||||
|
.dependent({
|
||||||
|
divTR: {is: divide, sig: (a: T, b: RealType<T>) => ({} as T)},
|
||||||
|
complex: {}
|
||||||
|
}, {
|
||||||
|
divide: dep => (z, r) =>
|
||||||
|
dep.complex(dep.divTR(z.re, r), dep.divTR(z.im, r))
|
||||||
|
})
|
||||||
|
.ship()
|
||||||
|
}
|
71
src/Complex/type.ts
Normal file
71
src/Complex/type.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import {implementations, commonSpecs} from '@/core/Dispatcher'
|
||||||
|
import {joinTypes} from '@/core/type'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CommonInterface, CommonSignature, NaNType, OneType, ZeroType
|
||||||
|
} from '@/interfaces/type'
|
||||||
|
|
||||||
|
export type Complex<T> = {re: T, im: T}
|
||||||
|
|
||||||
|
export const Complex_type = {
|
||||||
|
name: 'Complex',
|
||||||
|
test: <T>(dep: {testT: (z: unknown) => z is T}) =>
|
||||||
|
(z: unknown): z is Complex<T> =>
|
||||||
|
typeof z === 'object' && z != null && 're' in z && 'im' in z
|
||||||
|
&& dep.testT(z.re) && dep.testT(z.im),
|
||||||
|
infer: (dep: {typeOf: CommonSignature<undefined>['typeOf']}) =>
|
||||||
|
(z: Complex<unknown>) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)),
|
||||||
|
from: {
|
||||||
|
Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) =>
|
||||||
|
(z: Complex<U>) => ({re: dep.convert(z.re), im: dep.convert(z.im)}),
|
||||||
|
T: <T>(dep: {zero: CommonSignature<T>['zero']}) =>
|
||||||
|
(t: T) => ({re: t, im: dep.zero(t)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/interfaces/type" {
|
||||||
|
interface AssociatedTypes<T> {
|
||||||
|
Complex: T extends Complex<infer R> ? {
|
||||||
|
type: Complex<R>
|
||||||
|
zero: Complex<ZeroType<R>>
|
||||||
|
one: Complex<OneType<R> | ZeroType<R>>
|
||||||
|
nan: Complex<NaNType<R>>
|
||||||
|
real: RealType<R>
|
||||||
|
closure: T
|
||||||
|
} : never
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommonInterface<T, Aux> {
|
||||||
|
complex: (re: T, im?: T) => Complex<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal builder
|
||||||
|
const cplex = <T>(a:T, b:T): Complex<T> => ({re: a, im: b})
|
||||||
|
|
||||||
|
export function lift<T>() {
|
||||||
|
return implementations<CommonSignature<T>>()
|
||||||
|
.dependent({zero: {}}, {
|
||||||
|
complex: dep => (a, b) => cplex(a, b || dep.zero(a))
|
||||||
|
}).ship()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function <T>() {
|
||||||
|
const baseSignature = commonSpecs<T>()
|
||||||
|
|
||||||
|
return implementations<CommonSignature<Complex<T>>>()
|
||||||
|
.dependent(baseSignature({zero: {}}), {
|
||||||
|
zero: dep => z => cplex(dep.zero(z.re), dep.zero(z.re))
|
||||||
|
})
|
||||||
|
.dependent(baseSignature({zero: {}, one: {}}), {
|
||||||
|
one: dep => z =>
|
||||||
|
cplex<OneType<T> | ZeroType<T>>(dep.one(z.re), dep.zero(z.re))
|
||||||
|
})
|
||||||
|
.dependent(baseSignature({nan: {}}), {
|
||||||
|
nan: dep => z => cplex(dep.nan(z.re), dep.nan(z.re))
|
||||||
|
})
|
||||||
|
.dependent(baseSignature({re: {}}), {
|
||||||
|
re: dep => z => dep.re(z.re)
|
||||||
|
})
|
||||||
|
.ship()
|
||||||
|
}
|
2
src/all.ts
Normal file
2
src/all.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * as numbers from '@/numbers/all'
|
||||||
|
export * as Complex from '@/Complex/all'
|
4
src/core/Configuration.ts
Normal file
4
src/core/Configuration.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type Configuration = {
|
||||||
|
epsilon: number
|
||||||
|
predictable: boolean
|
||||||
|
}
|
201
src/core/Dispatcher.ts
Normal file
201
src/core/Dispatcher.ts
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import type {AnyFunc, CommonSignature, GenSigs} from '@/interfaces/type'
|
||||||
|
|
||||||
|
// A base type that roughly describes the dependencies of a single factory
|
||||||
|
// for implementations of one operation. It is an object whose keys are the
|
||||||
|
// identifiers are dependencies, and whose values describe that dependency.
|
||||||
|
// In the value for a given key, the 'is' property gives the name of the
|
||||||
|
// operation that dependency should be an instance of, defaulting to the key
|
||||||
|
// itself when not present, and the 'sig' property gives the desired
|
||||||
|
// signature for that operation. When the 'sig' property is not present,
|
||||||
|
// the signature will default to some ambient ensemble of signatures.
|
||||||
|
export type RawDependencies = Record<string, {is?: string, sig?: AnyFunc}>
|
||||||
|
|
||||||
|
// The following type transform fills in any unspecified signatures in RD
|
||||||
|
// with the corresponding signatures from SomeSigs:
|
||||||
|
type PatchedDepSpec<RD extends RawDependencies, SomeSigs extends GenSigs> = {
|
||||||
|
[K in keyof RD]: RD[K] extends {sig: AnyFunc}
|
||||||
|
? RD[K]
|
||||||
|
: K extends keyof SomeSigs ? (RD[K] & {sig: SomeSigs[K]}) : RD[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
// A factory for building dependency specifications from the ensemble of
|
||||||
|
// common signatures for a specific type (and perhaps auxiliary type). This
|
||||||
|
// is typically used when describing implementation factories for one type
|
||||||
|
// that depend on the common signatures for a *different* type.
|
||||||
|
export function commonSpecs<
|
||||||
|
T,
|
||||||
|
Aux = T,
|
||||||
|
CommonSigs extends GenSigs = CommonSignature<T, Aux>
|
||||||
|
>() {
|
||||||
|
return <RD extends RawDependencies>(
|
||||||
|
rd: RD
|
||||||
|
): PatchedDepSpec<RD, CommonSigs> => Object.fromEntries(
|
||||||
|
Object.keys(rd).map(k => [
|
||||||
|
k, 'sig' in rd[k] ? rd[k]
|
||||||
|
: {...rd[k], sig: (() => undefined)}
|
||||||
|
])
|
||||||
|
) as PatchedDepSpec<RD, CommonSigs>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Further constraint on a dependency specification that means it is ready
|
||||||
|
// to use with a given set of signatures:
|
||||||
|
type DepSpec<Signatures extends GenSigs, Needs extends string>
|
||||||
|
= {
|
||||||
|
[K in Needs]: K extends keyof Signatures
|
||||||
|
? {sig?: AnyFunc}
|
||||||
|
: {is: keyof Signatures, sig: AnyFunc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just checks if an RawDependencies is really a DepSpec, and blanks it out if not
|
||||||
|
type DepCheck<
|
||||||
|
RD extends RawDependencies,
|
||||||
|
Signatures extends GenSigs,
|
||||||
|
Needs extends string = keyof RD & string
|
||||||
|
> = RD extends DepSpec<Signatures, Needs> ? RD
|
||||||
|
: {
|
||||||
|
[K in Needs]: K extends keyof Signatures ? {}
|
||||||
|
: {is: never, sig: (q: boolean) => void}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actual type of a dependency, given a dependency specification
|
||||||
|
type DepType<
|
||||||
|
Signatures extends GenSigs,
|
||||||
|
DS extends DepSpec<Signatures, (keyof DS & string)>
|
||||||
|
> = {[K in keyof DS]: DS[K] extends {sig: AnyFunc}
|
||||||
|
? DS[K]['sig']
|
||||||
|
: K extends keyof Signatures ? Signatures[K] : never
|
||||||
|
}
|
||||||
|
|
||||||
|
// A collection of dependency specifications for some of the operations in
|
||||||
|
// an ensemble of Signatures:
|
||||||
|
type Specifications<
|
||||||
|
Signatures extends GenSigs,
|
||||||
|
NeedKeys extends keyof Signatures & string,
|
||||||
|
NeedList extends Record<NeedKeys, string>
|
||||||
|
> = {[K in NeedKeys]: DepSpec<Signatures, NeedList[K]>}
|
||||||
|
|
||||||
|
// The type of a factory function for implementations of a dependent operation,
|
||||||
|
// given a dependency specification:
|
||||||
|
type FactoryType<
|
||||||
|
Signatures extends GenSigs,
|
||||||
|
K extends (keyof Signatures) & string,
|
||||||
|
DS extends DepSpec<Signatures, (keyof DS & string)>
|
||||||
|
> = (dep: DepType<Signatures, DS>) => Signatures[K]
|
||||||
|
|
||||||
|
// The type of an implementation specification for an operation given its
|
||||||
|
// dependency specification: either directly the implementation if there
|
||||||
|
// are actually no dependencies, or a factory function and collection of
|
||||||
|
// dependency names otherwise:
|
||||||
|
type ImpType<
|
||||||
|
Signatures extends GenSigs,
|
||||||
|
K extends (keyof Signatures) & string,
|
||||||
|
DS extends DepSpec<Signatures, (keyof DS & string)>
|
||||||
|
> = DS extends null ? {implementation: Signatures[K]}
|
||||||
|
: {factory: FactoryType<Signatures, K, DS>, dependencies: DS}
|
||||||
|
|
||||||
|
// A collection of implementations for some operations of an ensemble of
|
||||||
|
// Signatures, matching a given collection of dependency specifications
|
||||||
|
type Implementations<
|
||||||
|
Signatures extends GenSigs,
|
||||||
|
NeedKeys extends keyof Signatures & string,
|
||||||
|
NeedList extends Record<NeedKeys, string>,
|
||||||
|
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||||
|
> = {[K in NeedKeys]: ImpType<Signatures, K, Specs[K]>}
|
||||||
|
|
||||||
|
// The builder interface that lets us assemble narrowly-typed Implementations:
|
||||||
|
interface ImplementationBuilder<
|
||||||
|
Signatures extends GenSigs,
|
||||||
|
NeedKeys extends keyof Signatures & string,
|
||||||
|
NeedList extends Record<NeedKeys, string>,
|
||||||
|
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||||
|
> {
|
||||||
|
independent<NewKeys extends (keyof Signatures) & string>(
|
||||||
|
independentImps: {[K in NewKeys]: Signatures[K]}
|
||||||
|
): ImplementationBuilder<
|
||||||
|
Signatures,
|
||||||
|
NeedKeys | NewKeys,
|
||||||
|
NeedList & {[K in NewKeys]: never},
|
||||||
|
Specs & {[K in NewKeys]: null}
|
||||||
|
>
|
||||||
|
|
||||||
|
dependent<
|
||||||
|
RD extends RawDependencies, // Easier to infer
|
||||||
|
NewKeys extends (keyof Signatures) & string,
|
||||||
|
DepKeys extends string = keyof RD & string
|
||||||
|
>(
|
||||||
|
depSpec: RD,
|
||||||
|
imps: {
|
||||||
|
[K in NewKeys]:
|
||||||
|
FactoryType<Signatures, K, DepCheck<RD, Signatures>>
|
||||||
|
}
|
||||||
|
): ImplementationBuilder<
|
||||||
|
Signatures,
|
||||||
|
NeedKeys | NewKeys,
|
||||||
|
NeedList & {[K in NewKeys]: DepKeys},
|
||||||
|
Specs & {[K in NewKeys]: DepCheck<RD, Signatures>}
|
||||||
|
>
|
||||||
|
|
||||||
|
ship(): Implementations<Signatures, NeedKeys, NeedList, Specs>
|
||||||
|
}
|
||||||
|
|
||||||
|
// And a function that actually provides the builder interface:
|
||||||
|
function impBuilder<
|
||||||
|
Signatures extends GenSigs,
|
||||||
|
NeedKeys extends keyof Signatures & string,
|
||||||
|
NeedList extends Record<NeedKeys, string>,
|
||||||
|
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||||
|
>(
|
||||||
|
sofar: Implementations<Signatures, NeedKeys, NeedList, Specs>
|
||||||
|
): ImplementationBuilder<Signatures, NeedKeys, NeedList, Specs> {
|
||||||
|
return {
|
||||||
|
independent<NewKeys extends (keyof Signatures) & string>(
|
||||||
|
imps: {[K in NewKeys]: Signatures[K]}) {
|
||||||
|
return impBuilder({
|
||||||
|
...sofar,
|
||||||
|
...Object.fromEntries(Object.keys(imps).map(k => [k, {
|
||||||
|
implementation: imps[k]
|
||||||
|
}]))
|
||||||
|
} as Implementations<
|
||||||
|
Signatures,
|
||||||
|
NeedKeys | NewKeys,
|
||||||
|
NeedList & {[K in NewKeys]: never},
|
||||||
|
Specs & {[K in NewKeys]: null}
|
||||||
|
>)
|
||||||
|
},
|
||||||
|
|
||||||
|
dependent<
|
||||||
|
RD extends RawDependencies,
|
||||||
|
NewKeys extends (keyof Signatures) & string,
|
||||||
|
DepKeys extends string = keyof RD & string
|
||||||
|
>(
|
||||||
|
depSpec: RD,
|
||||||
|
imps: {
|
||||||
|
[K in NewKeys]:
|
||||||
|
FactoryType<Signatures, K, DepCheck<RD, Signatures, DepKeys>>
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return impBuilder({
|
||||||
|
...sofar,
|
||||||
|
...Object.fromEntries(Object.keys(imps).map(k => [k, {
|
||||||
|
factory: imps[k],
|
||||||
|
dependencies: depSpec
|
||||||
|
}]))
|
||||||
|
}) as unknown as ImplementationBuilder<
|
||||||
|
Signatures,
|
||||||
|
NeedKeys | NewKeys,
|
||||||
|
NeedList & {[K in NewKeys]: DepKeys},
|
||||||
|
Specs & {[K in NewKeys]: DepCheck<RD, Signatures, DepKeys>}
|
||||||
|
>
|
||||||
|
},
|
||||||
|
ship() {
|
||||||
|
return (sofar as
|
||||||
|
Implementations<Signatures, NeedKeys, NeedList, Specs>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A convenience function that gives you an implementation builder:
|
||||||
|
export function implementations<Signatures extends GenSigs>(
|
||||||
|
): ImplementationBuilder<Signatures, never, {}, {}> {
|
||||||
|
return impBuilder({})
|
||||||
|
}
|
7
src/core/type.ts
Normal file
7
src/core/type.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type {TypeName} from '@/interfaces/type'
|
||||||
|
|
||||||
|
// Dummy implementation for now
|
||||||
|
export function joinTypes(a: TypeName, b: TypeName) {
|
||||||
|
if (a === b) return a
|
||||||
|
return 'any'
|
||||||
|
}
|
5
src/index.ts
Normal file
5
src/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import {inspect} from 'node:util'
|
||||||
|
|
||||||
|
import * as specifications from './all'
|
||||||
|
|
||||||
|
console.log(inspect(specifications, {depth: 8, colors: true}))
|
20
src/interfaces/arithmetic.ts
Normal file
20
src/interfaces/arithmetic.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type {ClosureType, NaNType, RealType} from './type'
|
||||||
|
|
||||||
|
type UnOp<T> = (a: T) => T
|
||||||
|
type BinOp<T> = (a: T, B: T) => T
|
||||||
|
|
||||||
|
declare module "./type" {
|
||||||
|
interface CommonInterface<T, Aux> {
|
||||||
|
add: BinOp<T>
|
||||||
|
unaryMinus: UnOp<T>
|
||||||
|
conj: UnOp<T>
|
||||||
|
subtract: BinOp<T>
|
||||||
|
multiply: BinOp<T>
|
||||||
|
square: UnOp<T>
|
||||||
|
absquare: (a: T) => RealType<T>
|
||||||
|
reciprocal: UnOp<T>
|
||||||
|
divide: BinOp<T>
|
||||||
|
conservativeSqrt: (a: T) => (T | NaNType<T>)
|
||||||
|
sqrt: (a: T) => (T | ClosureType<T>)
|
||||||
|
}
|
||||||
|
}
|
11
src/interfaces/relational.ts
Normal file
11
src/interfaces/relational.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type {NaNType} from './type.ts'
|
||||||
|
|
||||||
|
export type BinaryPredicate<T> = (a: T, b: T) => boolean
|
||||||
|
|
||||||
|
declare module "./type" {
|
||||||
|
interface CommonInterface<T, Aux> {
|
||||||
|
equal: BinaryPredicate<T>
|
||||||
|
unequal: BinaryPredicate<T>
|
||||||
|
isnan: (a: T | NaNType<T>) => a is NaNType<T>
|
||||||
|
}
|
||||||
|
}
|
116
src/interfaces/type.ts
Normal file
116
src/interfaces/type.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import {Configuration} from '@/core/Configuration'
|
||||||
|
|
||||||
|
/* First some type utilities: */
|
||||||
|
|
||||||
|
export type ToMapped<T> = {[K in keyof T]: T[K]}
|
||||||
|
|
||||||
|
export type UnionToIntersection<U> =
|
||||||
|
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
|
||||||
|
? I : never
|
||||||
|
|
||||||
|
export type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = {
|
||||||
|
[P in TKey]: (k: T[P]) => void
|
||||||
|
} [TKey] extends ((k: infer I) => void) ? I : never
|
||||||
|
|
||||||
|
|
||||||
|
// The following needs to be a type that is extended by
|
||||||
|
// the type of any function. Hopefully I have gotten this right.
|
||||||
|
export type AnyFunc = (...args: never[]) => unknown
|
||||||
|
|
||||||
|
// An ensemble of signatures of operations, for example, the CommonSignatures
|
||||||
|
// collected up by all of the modules
|
||||||
|
export type GenSigs = Record<string, AnyFunc>
|
||||||
|
|
||||||
|
/*****
|
||||||
|
* Every core math type has some associated types; they need
|
||||||
|
* to be published in the following interface. The key is the
|
||||||
|
* name of the type, and within the subinterface for that key,
|
||||||
|
* the type of the 'type' property is the actual TypeScript type
|
||||||
|
* we are associating the other properties to. To get an associated type,
|
||||||
|
* types are looked up by matching this 'type' property.
|
||||||
|
*
|
||||||
|
* Note the interface is generic with one parameter. To actually find
|
||||||
|
* the associated types of type T, instantiate the interface with type
|
||||||
|
* T. This mechanism deals with the fact that TypeScript doesn't really
|
||||||
|
* deal well with interfaces, some entries of which are generic and others
|
||||||
|
* are not.
|
||||||
|
****/
|
||||||
|
|
||||||
|
export interface AssociatedTypes<T> {
|
||||||
|
undefined: {
|
||||||
|
type: undefined
|
||||||
|
zero: undefined // The type of the zero of this type
|
||||||
|
one: undefined // The type of the multiplicative identity of this type
|
||||||
|
nan: undefined // The type of Not a Number of this type
|
||||||
|
real: undefined // The type of the real part of this type
|
||||||
|
closure: undefined // The type of the algebraic closure of this type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TypeName = string // Really should be some recursive definition,
|
||||||
|
// any key of AssociatedTypes that's not generic, or any key that is generic
|
||||||
|
// but instantatied by a TypeName. Not sure how to do that, so just go with
|
||||||
|
// any string for now.
|
||||||
|
|
||||||
|
type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined']
|
||||||
|
type ALookup<T, Name extends AssociatedTypeNames> =
|
||||||
|
ValueIntersectionByKeyUnion<
|
||||||
|
{[K in keyof AssociatedTypes<T>]:
|
||||||
|
T extends AssociatedTypes<T>[K]['type']
|
||||||
|
? AssociatedTypes<T>[K][Name] : unknown
|
||||||
|
},
|
||||||
|
keyof AssociatedTypes<T>
|
||||||
|
>
|
||||||
|
|
||||||
|
// For everything to compile, zero and one must be subtypes of T:
|
||||||
|
export type ZeroType<T> = ALookup<T, 'zero'> & T
|
||||||
|
export type OneType<T> = ALookup<T, 'one'> & T
|
||||||
|
// But I believe 'nan' really might not be, like I think we will have to use
|
||||||
|
// number NaN for the nan of 'bigint', as it has nothing at all like NaN,
|
||||||
|
// so don't force it:
|
||||||
|
export type NaNType<T> = ALookup<T, 'nan'>
|
||||||
|
export type RealType<T> = ALookup<T, 'real'>
|
||||||
|
export type ClosureType<T> = ALookup<T, 'closure'>
|
||||||
|
|
||||||
|
/*****
|
||||||
|
* The typical signature for every operation needs to be published in the
|
||||||
|
* following interface. Each key is the name of an operation.
|
||||||
|
* The type of each key should be the function type that the operation would
|
||||||
|
* "normally" have, understanding that there may be exceptions, which will\
|
||||||
|
* be dealt with by another mechanism.
|
||||||
|
*
|
||||||
|
* Note that this interface is generic in two parameters, the second of which
|
||||||
|
* defaults to the first. These are slots for type parameters to the typical
|
||||||
|
* signatures, most of which have only one type parameter.
|
||||||
|
****/
|
||||||
|
|
||||||
|
export interface CommonInterface<T, Aux = T> {
|
||||||
|
zero: (a: T) => ZeroType<T>
|
||||||
|
one: (a: T) => OneType<T>
|
||||||
|
nan: (a: T | NaNType<T>) => NaNType<T>
|
||||||
|
re: (a: T) => RealType<T>
|
||||||
|
|
||||||
|
config: () => Configuration
|
||||||
|
convert: (from: T) => Aux
|
||||||
|
typeOf: (x: unknown) => TypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommonSignature<T, Aux = T> = ToMapped<CommonInterface<T, Aux>>
|
||||||
|
export type SignatureKey<T, Aux = T> = keyof CommonSignature<T, Aux>
|
||||||
|
|
||||||
|
export function commonSignature<
|
||||||
|
K, T, Aux = T, CS extends GenSigs = CommonSignature<T, Aux>
|
||||||
|
>(): K extends keyof CS ? CS[K] : () => void {
|
||||||
|
return (() => undefined) as K extends keyof CS ? CS[K] : () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommonReturn<
|
||||||
|
K, T, Aux = T, CS extends GenSigs = CommonSignature<T, Aux>
|
||||||
|
> = K extends keyof CS ? ReturnType<CS[K]> : void
|
||||||
|
|
||||||
|
export type Dependency<T, Aux = T> = {
|
||||||
|
[K in SignatureKey<T, Aux>]: Pick<CommonSignature<T, Aux>, K>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Dependencies<T, Names extends SignatureKey<T>, Aux = T> =
|
||||||
|
Pick<CommonSignature<T, Aux>, Names>
|
2
src/numbers/all.ts
Normal file
2
src/numbers/all.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * as type_data from './type'
|
||||||
|
export * as arithmetic_functions from './arithmetic'
|
26
src/numbers/arithmetic.ts
Normal file
26
src/numbers/arithmetic.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {implementations} from '@/core/Dispatcher'
|
||||||
|
import type {CommonSignature} from '@/interfaces/type'
|
||||||
|
|
||||||
|
const conservativeSqrt = (a: number) => isNaN(a) ? NaN : Math.sqrt(a)
|
||||||
|
|
||||||
|
export default implementations<CommonSignature<number>>()
|
||||||
|
.independent({
|
||||||
|
add: (a, b) => a + b,
|
||||||
|
unaryMinus: a => -a,
|
||||||
|
subtract: (a, b) => a - b,
|
||||||
|
multiply: (a, b) => a * b,
|
||||||
|
absquare: a => a * a,
|
||||||
|
reciprocal: a => 1 / a,
|
||||||
|
divide: (a, b) => a / b,
|
||||||
|
conj: a => a,
|
||||||
|
conservativeSqrt })
|
||||||
|
.dependent({config: {}, complex: {}}, {
|
||||||
|
sqrt: dep => {
|
||||||
|
if (dep.config().predictable || !dep.complex) return conservativeSqrt
|
||||||
|
return a => {
|
||||||
|
if (isNaN(a)) return NaN
|
||||||
|
if (a >= 0) return Math.sqrt(a)
|
||||||
|
return dep.complex(0, Math.sqrt(-a))
|
||||||
|
}
|
||||||
|
}})
|
||||||
|
.ship()
|
31
src/numbers/type.ts
Normal file
31
src/numbers/type.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type {Complex} from '@/Complex/type'
|
||||||
|
import {implementations} from '@/core/Dispatcher'
|
||||||
|
import type {CommonSignature} from '@/interfaces/type'
|
||||||
|
|
||||||
|
export const number_type = {
|
||||||
|
name: 'number',
|
||||||
|
before: ['Complex'],
|
||||||
|
test: (n: unknown): n is number => typeof n === 'number',
|
||||||
|
from: {string: (s: string) => +s }
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/interfaces/type" {
|
||||||
|
interface AssociatedTypes<T> {
|
||||||
|
number: {
|
||||||
|
type: number
|
||||||
|
zero: 0
|
||||||
|
one: 1
|
||||||
|
nan: typeof NaN
|
||||||
|
real: number
|
||||||
|
closure: Complex<number>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default implementations<CommonSignature<number>>()
|
||||||
|
.independent({
|
||||||
|
zero: a => 0,
|
||||||
|
one: a => 1,
|
||||||
|
nan: a => NaN,
|
||||||
|
re: a => a
|
||||||
|
}).ship()
|
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"outDir": "./build",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"undici-types": [
|
||||||
|
"./node_modules/undici-types/index.d.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rootDir": "./src",
|
||||||
|
"target": "esnext",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user