feat: Add return typing strategies and implement sqrt with them (#26)
All checks were successful
/ test (push) Successful in 17s

Resolves #25

Reviewed-on: #26
Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
Glen Whitney 2025-04-28 16:29:33 +00:00 committed by Glen Whitney
parent aad62df8ac
commit 0765ba7202
35 changed files with 1125 additions and 152 deletions

14
etc/eslint.config.mjs Normal file
View file

@ -0,0 +1,14 @@
import {defineConfig} from 'eslint/config'
import js from '@eslint/js'
import globals from 'globals'
export default defineConfig([{
files: ['**/*.js'],
plugins: {js},
languageOptions: {globals: {...globals.mocha, ...globals.node}},
extends: ['js/recommended'],
rules: {
'no-unused-vars': ['error', {argsIgnorePattern: '^_'}],
'no-empty': ['error', {allowEmptyCatch: true}],
}
}])

View file

@ -4,6 +4,7 @@
description: 'Sequel to mathjs as prototype for mathjs overhaul',
scripts: {
test: 'mocha ./**/*.spec.js',
lint: 'eslint -c etc/eslint.config.mjs',
},
keywords: [
'math',
@ -16,6 +17,9 @@
url: 'https://code.studioinfinity.org/glen/nanomath.git',
},
devDependencies: {
'@eslint/js': '^9.25.1',
eslint: '^9.25.1',
globals: '^16.0.0',
mocha: '^11.1.0',
},
dependencies: {

479
pnpm-lock.yaml generated
View file

@ -12,12 +12,79 @@ importers:
specifier: ^2.1.3
version: 2.1.3
devDependencies:
'@eslint/js':
specifier: ^9.25.1
version: 9.25.1
eslint:
specifier: ^9.25.1
version: 9.25.1
globals:
specifier: ^16.0.0
version: 16.0.0
mocha:
specifier: ^11.1.0
version: 11.1.0
packages:
'@eslint-community/eslint-utils@4.6.1':
resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/regexpp@4.12.1':
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/config-array@0.20.0':
resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/config-helpers@0.2.1':
resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@0.13.0':
resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/eslintrc@3.3.1':
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/js@9.25.1':
resolution: {integrity: sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@2.1.6':
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.2.8':
resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
'@humanfs/node@0.16.6':
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
engines: {node: '>=18.18.0'}
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
engines: {node: '>=12.22'}
'@humanwhocodes/retry@0.3.1':
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
engines: {node: '>=18.18'}
'@humanwhocodes/retry@0.4.2':
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
engines: {node: '>=18.18'}
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@ -26,6 +93,25 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@types/estree@1.0.7':
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn@8.14.1:
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
engines: {node: '>=0.4.0'}
hasBin: true
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@ -63,6 +149,9 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
@ -73,6 +162,10 @@ packages:
browser-stdout@1.3.1:
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
@ -96,6 +189,9 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@ -113,6 +209,9 @@ packages:
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
engines: {node: '>=10'}
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
diff@5.2.0:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'}
@ -134,6 +233,61 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
eslint-scope@8.3.0:
resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-visitor-keys@4.2.0:
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint@9.25.1:
resolution: {integrity: sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
peerDependencies:
jiti: '*'
peerDependenciesMeta:
jiti:
optional: true
espree@10.3.0:
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
esquery@1.6.0:
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
engines: {node: '>=0.10'}
esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
estraverse@5.3.0:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@ -142,10 +296,17 @@ packages:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
flat-cache@4.0.1:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
flat@5.0.2:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@ -163,10 +324,22 @@ packages:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
globals@14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
globals@16.0.0:
resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==}
engines: {node: '>=18'}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@ -175,6 +348,18 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@ -213,10 +398,29 @@ packages:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
log-symbols@4.1.0:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
@ -224,6 +428,9 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
@ -244,10 +451,17 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@ -259,6 +473,10 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@ -275,6 +493,14 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -286,6 +512,10 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@ -336,11 +566,22 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
workerpool@6.5.1:
resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==}
@ -374,6 +615,63 @@ packages:
snapshots:
'@eslint-community/eslint-utils@4.6.1(eslint@9.25.1)':
dependencies:
eslint: 9.25.1
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
'@eslint/config-array@0.20.0':
dependencies:
'@eslint/object-schema': 2.1.6
debug: 4.4.0(supports-color@8.1.1)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
'@eslint/config-helpers@0.2.1': {}
'@eslint/core@0.13.0':
dependencies:
'@types/json-schema': 7.0.15
'@eslint/eslintrc@3.3.1':
dependencies:
ajv: 6.12.6
debug: 4.4.0(supports-color@8.1.1)
espree: 10.3.0
globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.1.0
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
'@eslint/js@9.25.1': {}
'@eslint/object-schema@2.1.6': {}
'@eslint/plugin-kit@0.2.8':
dependencies:
'@eslint/core': 0.13.0
levn: 0.4.1
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.6':
dependencies:
'@humanfs/core': 0.19.1
'@humanwhocodes/retry': 0.3.1
'@humanwhocodes/module-importer@1.0.1': {}
'@humanwhocodes/retry@0.3.1': {}
'@humanwhocodes/retry@0.4.2': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@ -386,6 +684,23 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@types/estree@1.0.7': {}
'@types/json-schema@7.0.15': {}
acorn-jsx@5.3.2(acorn@8.14.1):
dependencies:
acorn: 8.14.1
acorn@8.14.1: {}
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0
json-schema-traverse: 0.4.1
uri-js: 4.4.1
ansi-colors@4.1.3: {}
ansi-regex@5.0.1: {}
@ -411,6 +726,11 @@ snapshots:
binary-extensions@2.3.0: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
@ -421,6 +741,8 @@ snapshots:
browser-stdout@1.3.1: {}
callsites@3.1.0: {}
camelcase@6.3.0: {}
chalk@4.1.2:
@ -452,6 +774,8 @@ snapshots:
color-name@1.1.4: {}
concat-map@0.0.1: {}
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@ -466,6 +790,8 @@ snapshots:
decamelize@4.0.0: {}
deep-is@0.1.4: {}
diff@5.2.0: {}
eastasianwidth@0.2.0: {}
@ -478,6 +804,83 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-scope@8.3.0:
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
eslint-visitor-keys@3.4.3: {}
eslint-visitor-keys@4.2.0: {}
eslint@9.25.1:
dependencies:
'@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1)
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.20.0
'@eslint/config-helpers': 0.2.1
'@eslint/core': 0.13.0
'@eslint/eslintrc': 3.3.1
'@eslint/js': 9.25.1
'@eslint/plugin-kit': 0.2.8
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.2
'@types/estree': 1.0.7
'@types/json-schema': 7.0.15
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.0(supports-color@8.1.1)
escape-string-regexp: 4.0.0
eslint-scope: 8.3.0
eslint-visitor-keys: 4.2.0
espree: 10.3.0
esquery: 1.6.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
ignore: 5.3.2
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.4
transitivePeerDependencies:
- supports-color
espree@10.3.0:
dependencies:
acorn: 8.14.1
acorn-jsx: 5.3.2(acorn@8.14.1)
eslint-visitor-keys: 4.2.0
esquery@1.6.0:
dependencies:
estraverse: 5.3.0
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
estraverse@5.3.0: {}
esutils@2.0.3: {}
fast-deep-equal@3.1.3: {}
fast-json-stable-stringify@2.1.0: {}
fast-levenshtein@2.0.6: {}
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@ -487,8 +890,15 @@ snapshots:
locate-path: 6.0.0
path-exists: 4.0.0
flat-cache@4.0.1:
dependencies:
flatted: 3.3.3
keyv: 4.5.4
flat@5.0.2: {}
flatted@3.3.3: {}
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@ -503,6 +913,10 @@ snapshots:
dependencies:
is-glob: 4.0.3
glob-parent@6.0.2:
dependencies:
is-glob: 4.0.3
glob@10.4.5:
dependencies:
foreground-child: 3.3.1
@ -512,10 +926,23 @@ snapshots:
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
globals@14.0.0: {}
globals@16.0.0: {}
has-flag@4.0.0: {}
he@1.2.0: {}
ignore@5.3.2: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
imurmurhash@0.1.4: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
@ -546,10 +973,27 @@ snapshots:
dependencies:
argparse: 2.0.1
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
lodash.merge@4.6.2: {}
log-symbols@4.1.0:
dependencies:
chalk: 4.1.2
@ -557,6 +1001,10 @@ snapshots:
lru-cache@10.4.3: {}
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
minimatch@5.1.6:
dependencies:
brace-expansion: 2.0.1
@ -592,8 +1040,19 @@ snapshots:
ms@2.1.3: {}
natural-compare@1.4.0: {}
normalize-path@3.0.0: {}
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
fast-levenshtein: 2.0.6
levn: 0.4.1
prelude-ls: 1.2.1
type-check: 0.4.0
word-wrap: 1.2.5
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@ -604,6 +1063,10 @@ snapshots:
package-json-from-dist@1.0.1: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
path-exists@4.0.0: {}
path-key@3.1.1: {}
@ -615,6 +1078,10 @@ snapshots:
picomatch@2.3.1: {}
prelude-ls@1.2.1: {}
punycode@2.3.1: {}
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1
@ -625,6 +1092,8 @@ snapshots:
require-directory@2.1.1: {}
resolve-from@4.0.0: {}
safe-buffer@5.2.1: {}
serialize-javascript@6.0.2:
@ -673,10 +1142,20 @@ snapshots:
dependencies:
is-number: 7.0.0
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
which@2.0.2:
dependencies:
isexe: 2.0.0
word-wrap@1.2.5: {}
workerpool@6.5.1: {}
wrap-ansi@7.0.0:

View file

@ -10,4 +10,9 @@ describe('the numbers-only bundle', () => {
assert.strictEqual(math.isnan(-16.5), 0)
assert.strictEqual(math.isnan(NaN), 1)
})
it('takes sqrt with NaN for negative', () => {
assert.strictEqual(math.sqrt(25), 5)
assert(math.isnan(math.sqrt(-25)))
assert(math.isnan(math.sqrt(NaN)))
})
})

View file

@ -1,2 +1,3 @@
export * as typeDefinition from './BooleanT.js'
export * as type from './type.js'
export * as utilities from './utils.js'

View file

@ -1,6 +1,6 @@
import {BooleanT} from './BooleanT.js'
import {match} from '#core/TypePatterns.js'
import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
import {Returns, TypeOfTypes, Undefined} from '#core/Type.js'
import {NumberT} from '#number/NumberT.js'
const bool = f => Returns(BooleanT, f)

7
src/boolean/utils.js Normal file
View file

@ -0,0 +1,7 @@
import {BooleanT} from './BooleanT.js'
import {Returns} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
export const clone = match(BooleanT, Returns(BooleanT, p => p))
export const isnan = match(BooleanT, Returns(BooleanT, () => false))
export const isfinite = match(BooleanT, Returns(BooleanT, () => true))

View file

@ -11,7 +11,7 @@ function complexSpecialize(ComponentType) {
const typeName = `Complex(${ComponentType})`
if (ComponentType.concrete) {
const fromSpec = [match(
ComponentType, math => r => ({re: r, im: ComponentType.zero}))]
ComponentType, () => r => ({re: r, im: ComponentType.zero}))]
for (const {pattern, does} of ComponentType.from) {
fromSpec.push(match(
this.specialize(pattern.type),

View file

@ -1,9 +1,12 @@
import assert from 'assert'
import math from '#nanomath'
import {Complex} from '../Complex.js'
import {ReturnTyping} from '#core/Type.js'
import {NumberT} from '#number/NumberT.js'
const cplx = math.complex
const CplxNum = Complex(NumberT)
const {full} = ReturnTyping
describe('complex arithmetic operations', () => {
it('computes absquare of complex numbers', () => {
@ -25,23 +28,42 @@ describe('complex arithmetic operations', () => {
assert.deepStrictEqual(add(cplx(z,z), 10), cplx(cplx(13, 4), z))
assert.deepStrictEqual(
add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4)))
assert.strictEqual(add(z, math.conj(z)), 6)
const addFull = math.add.resolve([CplxNum, CplxNum], full)
assert.deepStrictEqual(addFull(z, math.conj(z)), cplx(6, 0))
})
it('adds real and complex numbers', () => {
const z = cplx(3, 4)
const zp3 = cplx(6,4)
const add = math.add
assert.deepStrictEqual(add(z, 3), zp3)
assert.deepStrictEqual(add(3, z), zp3)
assert.deepStrictEqual(add(3, cplx(z, z)), cplx(zp3, z))
assert.deepStrictEqual(add(cplx(z, z), 3), cplx(zp3, z))
})
it('conjugates complex numbers', () => {
const conj = math.conj
const z = cplx(3, 4)
assert.deepStrictEqual(conj(z), cplx(3, -4))
assert.deepStrictEqual(conj(cplx(z,z)), cplx(cplx(3, -4), cplx(-3, -4)))
const r = cplx(3, 0)
assert.strictEqual(conj(r), 3)
const conjFull = math.conj.resolve(CplxNum, full)
assert.deepStrictEqual(conjFull(r), cplx(3, -0))
})
it('divides complex numbers', () => {
const div = math.divide
const z = cplx(3, 4)
assert.deepStrictEqual(div(z, cplx(0, 1)), cplx(4, -3))
assert(math.equal(div(z, z), 1))
assert.strictEqual(div(z, z), 1)
const divFull = math.divide.resolve([CplxNum, CplxNum], full)
assert.deepStrictEqual(divFull(z, z), math.one(CplxNum))
// should probably have a quaternion example, but it's intricate
})
it('inverts complex numbers', () => {
const inv = math.invert
assert.deepStrictEqual(inv(cplx(0, 1)), cplx(0, -1))
assert.strictEqual(inv(cplx(2, 0)), 0.5)
assert.deepStrictEqual(inv(cplx(3, 4)), cplx(3/25, -4/25))
assert.deepStrictEqual(
inv(cplx(cplx(1, 2), cplx(4, 2))),
@ -51,8 +73,10 @@ describe('complex arithmetic operations', () => {
const mult = math.multiply
const z = cplx(3, 4)
assert.deepStrictEqual(mult(z, z), cplx(-7, 24))
assert(math.equal(mult(z, math.conj(z)), 25))
const q0 = cplx(cplx(1, 1), math.zero(Complex(NumberT)))
assert.strictEqual(mult(z, math.conj(z)), 25)
const multFull = math.multiply.resolve([CplxNum, CplxNum], full)
assert.deepStrictEqual(multFull(z, math.conj(z)), cplx(25, 0))
const q0 = cplx(cplx(1, 1), math.zero(CplxNum))
const q1 = cplx(cplx(1, 0.5), cplx(0.5, 0.75))
assert.deepStrictEqual(
mult(q0, q1), cplx(cplx(0.5, 1.5), cplx(1.25, 0.25)))
@ -60,6 +84,25 @@ describe('complex arithmetic operations', () => {
mult(q0, cplx(cplx(2, 0.1), cplx(1, 0.1))),
cplx(cplx(1.9, 2.1), cplx(1.1, -0.9)))
})
it('takes the square roots of complex numbers', () => {
const {sqrt, multiply} = math
const rhalf = Math.sqrt(1 / 2)
assert.deepStrictEqual(sqrt(cplx(1, 0)), 1)
assert.deepStrictEqual(sqrt(cplx(-1, 0)), cplx(0, 1))
assert(math.equal(sqrt(cplx(0, 1)), cplx(rhalf, rhalf)))
assert(math.equal(sqrt(cplx(0, -1)), cplx(rhalf, -rhalf)))
assert.deepStrictEqual(sqrt(cplx(5, 12)), cplx(3, 2))
const z = cplx(3, 4)
const rz = sqrt(z)
assert.deepStrictEqual(multiply(rz, rz), z)
// quaternions, too:
assert.deepStrictEqual(
sqrt(cplx(cplx(-46, 20), cplx(12, 16))),
cplx(cplx(2, 5), cplx(3, 4)))
const q = cplx(z, z)
const rq = sqrt(q)
assert(math.equal(multiply(rq, rq), q))
})
it('subtracts complex numbers', () => {
const z = cplx(3, 4)
const sub = math.subtract
@ -67,8 +110,15 @@ describe('complex arithmetic operations', () => {
assert.deepStrictEqual(sub(z, cplx(true, false)), cplx(2, 4))
assert.deepStrictEqual(sub(cplx(false, true), z), cplx(-3, -3))
assert.deepStrictEqual(
sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
cplx(cplx(2.5, 3.5), cplx(0, 0)))
sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)), cplx(2.5, 3.5))
assert.strictEqual(
sub(cplx(z, z), cplx(cplx(0, 4), z)), 3)
const subFull = math.subtract.resolve([
Complex(CplxNum), Complex(CplxNum)
], full)
assert.deepStrictEqual(
subFull(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
cplx(cplx(2.5, 3.5), math.zero(CplxNum)))
assert.deepStrictEqual(sub(z, 5), cplx(-2, 4))
assert.deepStrictEqual(sub(true, z), cplx(-2, -4))
assert.deepStrictEqual(sub(cplx(z,z), 10), cplx(cplx(-7, 4), z))

View file

@ -1,5 +1,9 @@
import assert from 'assert'
import math from '#nanomath'
import {Complex} from '../Complex.js'
import {OneOf, ReturnTyping} from '#core/Type.js'
import {NumberT} from '#number/NumberT.js'
const cplx = math.complex
@ -33,7 +37,21 @@ describe('complex type operations', () => {
assert(!assoc(cplx(0, 1), b))
})
it('computes cis of an angle', () => {
assert(math.equal(math.cis(0), 1))
assert(math.equal(math.cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2)))
const cis = math.cis.resolve(NumberT)
assert.strictEqual(cis.returns, OneOf(Complex(NumberT), NumberT))
assert.strictEqual(cis(0), 1)
assert.strictEqual(cis(Math.PI), -1)
assert(math.equal(cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2)))
math.config.returnTyping = ReturnTyping.full
const ccis = math.cis.resolve(NumberT)
assert.strictEqual(ccis.returns, Complex(NumberT))
const one = ccis(0)
assert(one !== 1)
assert(math.equal(one, 1))
assert(math.equal(ccis(Math.PI), cplx(-1)))
math.config.returnTyping = ReturnTyping.free
assert.strictEqual(math.cis.resolve(NumberT), cis)
assert.strictEqual(math.cis.resolve(NumberT, ReturnTyping.full), ccis)
assert.strictEqual(math.cis(2*Math.PI), 1)
})
})

View file

@ -0,0 +1,51 @@
import assert from 'assert'
import math from '#nanomath'
const cplx = math.complex
describe('complex utilities', () => {
it('clones a complex', () => {
const z = cplx(3, 4)
const cz = math.clone(z)
assert(cz !== z)
assert.deepStrictEqual(z, cz)
const q = cplx(z, math.add(z, 1))
const cq = math.clone(q)
assert(cq !== q)
assert.deepStrictEqual(q, cq)
assert(q.re !== cq.re && q.im !== cq.im)
})
it('checks for nan', () => {
assert(math.isnan(cplx(NaN, NaN)))
assert(!math.isnan(cplx(NaN, 6.28)))
})
it('tests for finiteness', () => {
const fin = math.isfinite
assert(fin(cplx(3, 4)))
assert(!fin(cplx(2, Infinity)))
assert(!fin(cplx(NaN, 0)))
})
it('identifies Gaussian integers', () => {
const isInt = math.isInteger
assert(isInt(cplx(3, 4)))
assert(isInt(cplx(-37,0)))
assert(!isInt(cplx(99, -1.000001)))
})
it('identifies real numbers', () => {
const {isReal} = math
assert(isReal(cplx(5, 0)))
assert(isReal(cplx(5, -1e-17)))
assert(!isReal(cplx(5, 0.000001)))
assert(isReal(cplx(cplx(5, 1e-16), cplx(-0, 0))))
assert(!isReal(cplx(cplx(5, 2), cplx(0, 0))))
assert(!isReal(cplx(cplx(5, 0), cplx(0, 0.00002))))
})
it('identifies complex numbers that only have a real part', () => {
const noImag = math.nonImaginary
assert(noImag(cplx(5, 0)))
assert(noImag(cplx(5, -1e-17)))
assert(!noImag(cplx(5, 0.000001)))
assert(noImag(cplx(cplx(5, 2), cplx(0, 0))))
assert(!noImag(cplx(cplx(5, 0), cplx(0, 0.00002))))
})
})

View file

@ -1,43 +1,46 @@
import {Complex} from './Complex.js'
import {promoteBinary, promoteUnary} from './helpers.js'
import {maybeComplex, promoteBinary, promoteUnary} from './helpers.js'
import {ResolutionError} from '#core/helpers.js'
import {Returns, ReturnTyping} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js'
export const absquare = match(Complex, (math, C) => {
const compAbsq = math.absquare.resolve([C.Component])
const {conservative, full, free} = ReturnTyping
export const absquare = match(Complex, (math, C, strategy) => {
const compAbsq = math.absquare.resolve(C.Component, full)
const R = compAbsq.returns
const add = math.add.resolve([R,R])
const add = math.add.resolve([R,R], strategy)
return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im)))
})
export const add = promoteBinary('add')
export const conj = match(Complex, (math, C) => {
const neg = math.negate.resolve(C.Component)
const compConj = math.conj.resolve(C.Component)
const cplx = math.complex.resolve([compConj.returns, neg.returns])
export const conj = match(Complex, (math, C, strategy) => {
const neg = math.negate.resolve(C.Component, full)
const compConj = math.conj.resolve(C.Component, full)
const cplx = maybeComplex(math, strategy, compConj.returns, neg.returns)
return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im)))
})
export const divide = [
match([Complex, T => !T.complex], (math, [C, R]) => {
const div = math.divide.resolve([C.Component, R])
const cplx = math.complex.resolve([div.returns, div.returns])
match([Complex, T => !T.complex], (math, [C, R], strategy) => {
const div = math.divide.resolve([C.Component, R], full)
const cplx = maybeComplex(math, strategy, div.returns, div.returns)
return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r)))
}),
match([Complex, Complex], (math, [W, Z]) => {
const inv = math.invert.resolve(Z)
const mult = math.multiply.resolve([W, inv.returns])
match([Complex, Complex], (math, [W, Z], strategy) => {
const inv = math.invert.resolve(Z, full)
const mult = math.multiply.resolve([W, inv.returns], strategy)
return ReturnsAs(mult, (w, z) => mult(w, inv(z)))
})
]
export const invert = match(Complex, (math, C) => {
const conj = math.conj.resolve(C)
const norm = math.absquare.resolve(C)
const div = math.divide.resolve([C.Component, norm.returns])
const cplx = math.complex.resolve([div.returns, div.returns])
export const invert = match(Complex, (math, C, strategy) => {
const conj = math.conj.resolve(C, full)
const norm = math.absquare.resolve(C, full)
const div = math.divide.resolve([C.Component, norm.returns], full)
const cplx = maybeComplex(math, strategy, div.returns, div.returns)
return ReturnsAs(cplx, z => {
const c = conj(z)
const d = norm(z)
@ -47,23 +50,87 @@ export const invert = match(Complex, (math, C) => {
// We want this to work for complex numbers, quaternions, octonions, etc
// See https://math.ucr.edu/home/baez/octonions/node5.html
export const multiply = match([Complex, Complex], (math, [W, Z]) => {
const conj = math.conj.resolve(W.Component)
if (conj.returns !== W.Component) {
throw new ResolutionError(
`conjugation on ${W.Component} returns other type (${conj.returns})`)
}
const mWZ = math.multiply.resolve([W.Component, Z.Component])
const mZW = math.multiply.resolve([Z.Component, W.Component])
const sub = math.subtract.resolve([mWZ.returns, mZW.returns])
const add = math.add.resolve([mWZ.returns, mZW.returns])
const cplx = math.complex.resolve([sub.returns, add.returns])
return ReturnsAs(cplx, (w, z) => {
const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im)))
const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im))
return cplx(real, imag)
export const multiply = [
match([T => !T.complex, Complex], (math, [R, C], strategy) => {
const mult = math.multiply.resolve([R, C.Component], full)
const cplx = maybeComplex(math, strategy, mult.returns, mult.returns)
return ReturnsAs(cplx, (r, z) => cplx(mult(r, z.re), mult(r, z.im)))
}),
match([Complex, T => !T.complex], (math, [C, R], strategy) => {
const mult = math.multiply.resolve([R, C], strategy)
return ReturnsAs(mult, (z, r) => mult(r, z))
}),
match([Complex, Complex], (math, [W, Z], strategy) => {
const conj = math.conj.resolve(W.Component, full)
if (conj.returns !== W.Component) {
throw new ResolutionError(
`conjugation on ${W.Component} returns type (${conj.returns})`)
}
const mWZ = math.multiply.resolve([W.Component, Z.Component], full)
const mZW = math.multiply.resolve([Z.Component, W.Component], full)
const sub = math.subtract.resolve([mWZ.returns, mZW.returns], full)
const add = math.add.resolve([mWZ.returns, mZW.returns], full)
const cplx = maybeComplex(math, strategy, sub.returns, add.returns)
return ReturnsAs(cplx, (w, z) => {
const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im)))
const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im))
return cplx(real, imag)
})
})
})
]
export const negate = promoteUnary('negate')
// Should work for complex, quaternions, octonions, etc., even with
// integer coordinates.
export const sqrt = match(Complex, (math, C, strategy) => {
const re = math.re.resolve(C)
const R = re.returns
const isReal = math.isReal.resolve(C)
// dependencies for the real case:
const zComp = math.zero(C.Component)
const sign = math.sign.resolve(R, conservative)
const oneR = math.one(R)
const zR = math.zero(R)
const addRComp = math.add.resolve([R, C.Component], full)
const sqrtR = math.sqrt.resolve(R, conservative)
const neg = math.negate.resolve(R, conservative)
const cplx = math.complex.resolve([C.Component, C.Component], full)
// additional dependencies for the complex case
const abs = math.abs.resolve(C, full)
if (abs.returns !== R) {
throw new TypeError(`abs on ${C} returns ${abs.returns}, not ${R}`)
}
const addRR = math.add.resolve([R, R], conservative)
const twoR = addRR(oneR, oneR)
const multRR = math.multiply.resolve([R, R], conservative)
const im = math.im.resolve(C, full)
const addRC = math.add.resolve([R, C], full)
const divRR = math.divide.resolve([R, R], conservative)
const divCR = math.divide.resolve([C, R], full)
// The guts of the computation:
const sqrtImp = Returns(C, z => {
const a = re(z)
if (isReal(z)) { // always a special case
let rp = zComp
let ip = zComp
const sgn = sign(a)
if (sgn === oneR) rp = addRComp(sqrtR(a), rp)
else if (sgn !== zR) ip = addRComp(sqrtR(neg(a)), ip)
return cplx(rp, ip)
}
// Complex case:
// We can write z = a + q where q is pure imaginary.
// Let s = sqrt(2(|z|+a)). Then sqrt(z) = (s/2) + (q/s).
const s = sqrtR(multRR(twoR, addRR(abs(z), a)))
const q = im(z)
return addRC(divRR(s, twoR), divCR(q, s))
})
if (strategy != free) return sqrtImp
const prune = math.pruneImaginary.resolve(C, free)
return ReturnsAs(prune, z => prune(sqrtImp(z)))
})
export const subtract = promoteBinary('subtract')

View file

@ -1,20 +1,38 @@
import {Complex} from './Complex.js'
import {ReturnTyping} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js'
export const promoteUnary = name => match(
const {free, full} = ReturnTyping
export const maybeComplex = (math, strategy, Real, Imag) => {
if (strategy !== free) return math.complex.resolve([Real, Imag], strategy)
const cplx = math.complex.resolve([Real, Imag], full)
const prune = math.pruneImaginary.resolve(cplx.returns, full)
return ReturnsAs(prune, (r, m) => prune(cplx(r, m)))
}
export const promoteUnary = (name, overrideStrategy) => match(
Complex,
(math, C) => {
const compOp = math.resolve(name, C.Component)
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
(math, C, strategy) => {
const compOp = math.resolve(name, C.Component, full)
if (overrideStrategy) strategy = overrideStrategy
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns)
return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im)))
})
export const promotePredicateAnd = name => match(
Complex,
(math, C, strategy) => {
const compPred = math.resolve(name, C.Component, strategy)
return ReturnsAs(compPred, z => compPred(z.re) && compPred(z.im))
})
export const promoteBinary = name => match(
[Complex, Complex],
(math, [W, Z]) => {
const compOp = math.resolve(name, [W.Component, Z.Component])
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
(math, [W, Z], strategy) => {
const compOp = math.resolve(name, [W.Component, Z.Component], full)
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns)
return ReturnsAs(
cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im)))
})

View file

@ -1,9 +1,11 @@
import {Complex} from './Complex.js'
import {Returns} from "#core/Type.js"
import {OneOf, Returns, ReturnTyping, TypeOfTypes} from "#core/Type.js"
import {Any, match} from "#core/TypePatterns.js"
import {BooleanT} from '#boolean/BooleanT.js'
import {NumberT} from '#number/NumberT.js'
const {free, full} = ReturnTyping
export const complex = [
match(Any, (math, T) => {
const z = math.zero(T)
@ -27,29 +29,81 @@ export const complex = [
})
]
export const arg = match(
Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re)))
export const arg = // [ // enable when we have atan2 in mathjs
// match(Complex, (math, C) => {
// const re = math.re.resolve(C)
// const R = re.returns
// const im = math.im.resolve(C)
// const abs = math.abs.resolve(C)
// const atan2 = math.atan2.resolve([R, R], conservative)
// return Returns(R, z => atan2(abs(im(z)), re(z)))
// }), // note always between 0 and tau/2; need to use in conjunction
// // with a complex unit function that gives you the proper
// // imaginary unit, ±i in the simple complex case, to restore the
// // full circle of values for the direction of a complex number
match(Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re)))
//]
/* Returns true if w is z multiplied by a complex unit */
export const associate = match([Complex, Complex], (math, [W, Z]) => {
if (Z.Component.complex) {
throw new Error(
`The group of units of type ${Z} is not yet implemented`)
}
const eq = math.equal.resolve([W, Z])
const neg = math.negate.resolve(Z)
const eqN = math.equal.resolve([W, neg.returns])
const mult = math.multiply.resolve([Z, Z])
const eqM = math.equal.resolve([W, mult.returns])
const negM = math.negate.resolve(mult.returns)
const eqNM = math.equal.resolve([W, negM.returns])
const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component))
return Returns(BooleanT, (w, z) => {
if (eq(w, z) || eqN(w, neg(z))) return true
const iz = mult(iZ, z)
return eqM(w, iz) || eqNM(w, negM(iz))
})
export const associate = match(
[Complex, Complex],
(math, [W, Z]) => {
if (Z.Component.complex) {
throw new Error(
`The group of units of type ${Z} is not yet implemented`)
}
const eq = math.equal.resolve([W, Z], full)
const neg = math.negate.resolve(Z, full)
const eqN = math.equal.resolve([W, neg.returns], full)
const mult = math.multiply.resolve([Z, Z], full)
const eqM = math.equal.resolve([W, mult.returns], full)
const negM = math.negate.resolve(mult.returns, full)
const eqNM = math.equal.resolve([W, negM.returns], full)
const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component))
return Returns(BooleanT, (w, z) => {
if (eq(w, z) || eqN(w, neg(z))) return true
const iz = mult(iZ, z)
return eqM(w, iz) || eqNM(w, negM(iz))
})
})
const _cis = t => ({re: Math.cos(t), im: Math.sin(t)})
export const cis = match(NumberT, (math, _type, strategy) => {
if (strategy === free) {
const intTest = math.isInteger.resolve(NumberT)
return Returns(OneOf(NumberT, Complex(NumberT)), t => {
let halfCycles = t / Math.PI
if (intTest(halfCycles)) {
halfCycles = Math.round(halfCycles)
return halfCycles % 2 ? -1 : 1
}
return _cis(t)
})
}
return Returns(Complex(NumberT), _cis)
})
export const cis = match(NumberT, Returns(Complex(NumberT), t => ({
re: Math.cos(t), im: Math.sin(t)})))
// Returns the real part if the complex number supplied has no imaginary
// part, otherwise leaves that complex number alone.
// Since it is explicitly asking for varying type, it ignores strategy.
export const pruneImaginary = match(Complex, (math, C) => {
const outcomes = [C]
let T = C
while (T.complex) {
T = T.Component
outcomes.push(T)
}
const noImag = math.nonImaginary.resolve(C, full)
if (C.Component.complex) {
const compPrune = math.pruneImaginary.resolve(C.Component)
return Returns(OneOf(...outcomes), z => noImag(z) ? compPrune(z.re) : z)
}
return Returns(OneOf(...outcomes), z => noImag(z) ? z.re : z)
})
// Returns the type of re(z)
export const Real = match(TypeOfTypes, Returns(TypeOfTypes, t => {
while (t.complex) t = t.Component
return t
}))

View file

@ -1,9 +1,54 @@
import {Complex} from './Complex.js'
import {promotePredicateAnd, promoteUnary} from './helpers.js'
import {Returns, ReturnTyping} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js'
const {full} = ReturnTyping
export const clone = promoteUnary('clone', full)
export const isnan = promotePredicateAnd('isnan')
export const isfinite = promotePredicateAnd('isfinite')
// Note: the followig predicate returns true for all Gaussian integers, not
// just so-called rational integers.
export const isInteger = promotePredicateAnd('isInteger')
// true if the Complex is truly a real number (nonImaginary and its
// real part is a real number)
export const isReal = match(Complex, (math, C) => {
const eq = math.equal.resolve([C.Component, C.Component])
const add = math.add.resolve([C.Component, C.Component])
return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im)))
const nonImag = math.nonImaginary.resolve(C, full)
const realComp = math.isReal.resolve(C.Component)
return ReturnsAs(realComp, z => nonImag(z) && realComp(z.re))
})
// true if the imaginary part of a Complex is negligible compared to the real
export const nonImaginary = match(Complex, (math, C) => {
const eq = math.equal.resolve([C.Component, C.Component])
const add = math.add.resolve([C.Component, C.Component], full)
return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im)))
})
// Always returns the "true" real part of a complex number z (i.e., the part
// that is actually in the real numbers mathematically). In other words,
// performs z.re recursively until it gets something not Complex.
export const re = match(Complex, (math, C) => {
return Returns(math.Real(C), z => {
let T = C
while (T.complex) {
T = T.Component
z = z.re
}
return z
})
})
// Returns everything but the real part of z. NOTE this is a complex
// number with zero real part, _not_ the coefficient of the imaginary
// component of z. If you want the latter, just use `z.im`
export const im = match(Complex, (math, C) => {
const imComp = math.im.resolve(C.Component, full)
const cplx = math.complex.resolve([C.Component, C.Component], full)
return ReturnsAs(cplx, z => cplx(imComp(z.re), z.im))
})

View file

@ -66,9 +66,30 @@ export const Undefined = new Type(
t => typeof t === 'undefined',
{zero: undefined, one: undefined, nan: undefined})
export const TypeOfTypes = new Type(t => t instanceof Type)
export const NotAType = new Type(t => true) // Danger, do not merge!
export const NotAType = new Type(() => true) // Danger, do not merge!
NotAType._doNotMerge = true
const unionDirectory = new ArrayKeyedMap() // make sure only one of each union
export const OneOf = (...types) => {
const nonType = types.findIndex(T => !(T instanceof Type))
if (nonType >= 0) {
throw new RangeError(
`OneOf can only take type arguments, not ${types[nonType]}`)
}
const typeSet = new Set(types) // remove duplicates
const typeList = Array.from(typeSet).sort() // canonical order
const generic = typeList.find(T => !T.concrete)
if (generic) {
throw new RangeError(`OneOf can only take concrete types, not ${generic}`)
}
if (!unionDirectory.has(typeList)) {
unionDirectory.set(typeList, new Type(
t => typeList.some(T => T.test(t)),
{typeName: typeList.join('|')}))
}
return unionDirectory.get(typeList)
}
export const Returns = (type, f) => (f.returns = type, f)
export const whichType = typs => Returns(TypeOfTypes, item => {
@ -84,3 +105,17 @@ export const whichType = typs => Returns(TypeOfTypes, item => {
}
throw new TypeError(errorMsg)
})
// The return typing strategies
// MAKE SURE NONE ARE FALSY, so that code can easily test whether a strategy
// has been specified.
export const ReturnTyping = Object.freeze({
free: 1,
conservative: 2,
full: 3,
name(strat) {
for (const key in this) {
if (this[key] === strat) return key
}
}
})

View file

@ -3,7 +3,7 @@ import ArrayKeyedMap from 'array-keyed-map'
import {ResolutionError, isPlainFunction, isPlainObject} from './helpers.js'
import {Implementations, ImplementationsGenerator} from './Implementations.js'
import {bootstrapTypes} from './type.js'
import {Returns, whichType, Type} from './Type.js'
import {Returns, ReturnTyping, whichType, Type} from './Type.js'
import {
matched, needsCollection, Passthru, Matcher, match
} from './TypePatterns.js'
@ -155,7 +155,8 @@ export class TypeDispatcher {
const types = args.map(thisTypeOf)
return this.resolve(key, types)(...args)
}
standard.resolve = (types) => this.resolve(key, types)
standard.resolve =
(types, strategy) => this.resolve(key, types, strategy)
standard.isDispatcher = true
Object.defineProperty(this, key, {
enumerable: true,
@ -167,14 +168,18 @@ export class TypeDispatcher {
if (typeof tryValue === 'object') {
if (!('resolve' in tryValue)) {
tryValue.resolve = types => this.resolve(key, types)
tryValue.resolve =
(types, strat) => this.resolve(key, types, strat)
}
const get = () => {
const keyDeps = this._dependencies.get(key)
if (!keyDeps) return tryValue
const watch = Array.from(keyDeps.keys().filter(
types => types.length === 0
|| this.resolve(key, types) === tryValue
bhvix => {
if (bhvix.length < 2) return true
const types = bhvix.slice(1)
return this.resolve(key, types) === tryValue
}
))
if (watch.length) {
return DependencyWatcher(tryValue, key, watch, this)
@ -221,7 +226,7 @@ export class TypeDispatcher {
}
}
_addToDeps(key, types) {
_addToDeps(key, bhvix) {
// Never depend on internal methods:
if (key.startsWith('_')) return
let depMap = this._dependencies.get(key)
@ -229,11 +234,11 @@ export class TypeDispatcher {
depMap = new ArrayKeyedMap()
this._dependencies.set(key, depMap)
}
if (!depMap.has(types)) depMap.set(types, new Map())
const depColl = depMap.get(types)
for (const [dkey, types] of this.resolve._genDepsOf) {
if (!depMap.has(bhvix)) depMap.set(bhvix, new Map())
const depColl = depMap.get(bhvix)
for (const [dkey, bhvix] of this.resolve._genDepsOf) {
if (!depColl.has(dkey)) depColl.set(dkey, new ArrayKeyedMap())
depColl.get(dkey).set(types, true)
depColl.get(dkey).set(bhvix, true)
}
}
@ -259,28 +264,42 @@ export class TypeDispatcher {
return args => extractors.map(f => f(args))
}
resolve(key, types) {
// key is the identifier of the method being looked up
// types is a single argument type or an array of actual argument types
// strategy is a ReturnTyping value specifying how to choose the
// return type for the operation.
resolve(key, types, strategy) {
if (!(key in this)) {
throw new ReferenceError(`no method or value for key '${key}'`)
}
if (!Array.isArray(types)) types = [types]
if (!strategy) {
// Avoid recursing on obtaining config
if (key === 'config') strategy = ReturnTyping.free
else strategy = this.config.returnTyping
}
// The "behavior index": the return type strategy followed by the
// types:
const bhvix = [strategy, ...types]
const generatingDeps = this.resolve._genDepsOf?.length
if (generatingDeps) this._addToDeps(key, types)
if (generatingDeps) this._addToDeps(key, bhvix)
const behave = this._behaviors[key]
// Return the cached resolution if it's there
if (behave.has(types)) {
const result = behave.get(types)
if (behave.has(bhvix)) {
const result = behave.get(bhvix)
if (result === underResolution) {
throw new ResolutionError(
`recursive resolution of ${key} on ${types}`)
`recursive resolution of ${key} on ${types} with return typing `
+ ReturnTyping.name(strategy)
)
}
if (generatingDeps
&& typeof result === 'object'
&& !(result instanceof Type)
) {
return DependencyRecorder(result, key, this, types)
return DependencyRecorder(result, key, this, bhvix)
}
return result
}
@ -315,12 +334,12 @@ export class TypeDispatcher {
}
// If this key is producing a non-function value, we're done
if (!isPlainFunction(item)) {
behave.set(types, item)
behave.set(bhvix, item)
if (generatingDeps
&& typeof item === 'object'
&& !(item instanceof Type)
) {
return DependencyRecorder(item, key, this, types)
return DependencyRecorder(item, key, this, bhvix)
}
return item
}
@ -334,17 +353,19 @@ export class TypeDispatcher {
let theBehavior = () => undefined
let finalBehavior
this.resolve._genDepsOf.push([key, types])
behave.set(types, underResolution)
this.resolve._genDepsOf.push([key, bhvix])
behave.set(bhvix, underResolution)
try { // Used to make sure not to return without popping _genDepsOf
if (!('returns' in item)) {
// looks like a factory
try {
theBehavior = item(
DependencyRecorder(this, '', this, []),
matched(template, this))
matched(template, this),
strategy)
} catch (e) {
e.message = `Error in factory for ${key} on ${types} `
+ `with return typing ${ReturnTyping.name(strategy)} `
+ `(match data ${template}): ${e.message}`
throw e
}
@ -355,7 +376,8 @@ export class TypeDispatcher {
const returning = theBehavior.returns
if (!returning) {
throw new TypeError(
`No return type specified for ${key} on ${types}`)
`No return type specified for ${key} on ${types} with`
+ ` return typing ${ReturnTyping.name(strategy)}`)
}
if (needsCollection(template)) {
// have to wrap the behavior to collect the actual arguments
@ -369,7 +391,7 @@ export class TypeDispatcher {
} finally {
this.resolve._genDepsOf.pop() // OK, now it's safe to return
}
behave.set(types, finalBehavior)
behave.set(bhvix, finalBehavior)
finalBehavior.template = template
return finalBehavior
}
@ -380,8 +402,8 @@ export class TypeDispatcher {
_invalidate(depColl) {
if (!depColl) return
for (const [key, typeMap] of depColl) {
for (const types of typeMap.keys()) {
this._behaviors[key].delete(types)
for (const bhvix of typeMap.keys()) {
this._behaviors[key].delete(bhvix)
}
}
}
@ -389,25 +411,26 @@ export class TypeDispatcher {
_disengageFallback(key) {
// We need to find all of the behaviors that currently rely on the
// fallback, invalidate their dependencies, and remove them.
const fallTypes = []
const fallIxes = []
const behs = this._behaviors[key]
const imps = this._implementations[key]
const deps = this._dependencies.get(key)
for (const types of behs.keys()) {
for (const bhvix of behs.keys()) {
let fallsback = true
for (const [pattern] of imps) {
const types = bhvix.length ? bhvix.slice(1) : bhvix
const [finalIndex] = pattern.match(types)
if (finalIndex === types.length) {
fallsback = false
break
}
}
if (fallsback) fallTypes.push(types)
if (fallsback) fallIxes.push(bhvix)
}
for (const types of fallTypes) {
const depColl = deps?.get(types)
for (const bhvix of fallIxes) {
const depColl = deps?.get(bhvix)
if (depColl?.size) this._invalidate(depColl)
behs.delete(types)
behs.delete(bhvix)
}
}
@ -415,20 +438,21 @@ export class TypeDispatcher {
// like disengageFallback, just we have the offending pattern:
const behs = this._behaviors[key]
const deps = this._dependencies.get(key)
const patTypes = behs.keys().filter(types => {
const patIxes = behs.keys().filter(bhvix => {
const types = bhvix.length ? bhvix.slice(1) : bhvix
const [finalIndex] = pattern.match(types)
return finalIndex === types.length
})
for (const types of patTypes) {
const depColl = deps?.get(types)
for (const bhvix of patIxes) {
const depColl = deps?.get(bhvix)
if (depColl?.size) this._invalidate(depColl)
behs.delete(types)
behs.delete(bhvix)
}
}
}
// Proxy that traps accesses and records dependencies on them
const DependencyRecorder = (object, path, repo, types) => new Proxy(object, {
const DependencyRecorder = (object, path, repo, bhvix) => new Proxy(object, {
get(target, prop, receiver) {
const result = Reflect.get(target, prop, receiver)
// pass internal methods through, as well as resolve calls,
@ -443,27 +467,27 @@ const DependencyRecorder = (object, path, repo, types) => new Proxy(object, {
// OK, it's not a method on a TypeDispatcher, it's some other kind of
// value. So first record the dependency on prop at this path.
const newPath = path ? [path, prop].join('.') : prop
repo._addToDeps(newPath, types)
repo._addToDeps(newPath, bhvix)
// Now, if the result is an object, we may need to record further
// dependencies on its properties (e.g. math.config.predictable)
// So proxy the return value, except for types, which must maintain
// strict referential identity:
if (typeof result === 'object' && !(result instanceof Type)) {
return DependencyRecorder(result, newPath, repo, types)
return DependencyRecorder(result, newPath, repo, bhvix)
} else return result
}
})
// The flip side: proxy that traps setting properties and invalidates things
// that depend on them:
const DependencyWatcher = (object, path, typesList, repo) => new Proxy(object, {
const DependencyWatcher = (object, path, ixList, repo) => new Proxy(object, {
set(target, prop, value, receiver) {
// First see if this setting has any dependencies:
const newPath = [path, prop].join('.')
const depPerTypes = repo._dependencies.get(newPath)
if (depPerTypes && Reflect.get(target, prop, receiver) !== value) {
for (const types of typesList) {
repo._invalidate(depPerTypes.get(types))
for (const bhvix of ixList) {
repo._invalidate(depPerTypes.get(bhvix))
}
}
// Now we can just perform the setting
@ -474,7 +498,7 @@ const DependencyWatcher = (object, path, typesList, repo) => new Proxy(object, {
const result = Reflect.get(target, prop, receiver)
if (typeof result === 'object' && !(result instanceof Type)) {
const newPath = [path, prop].join('.')
return DependencyWatcher(result, newPath, typesList, repo)
return DependencyWatcher(result, newPath, ixList, repo)
}
return result
}

View file

@ -2,7 +2,7 @@ import {Type, Undefined} from './Type.js'
import {isPlainFunction} from './helpers.js'
export class TypePattern {
match(typeSequence, options={}) {
match(_typeSequence, _options={}) {
throw new Error('Specific TypePatterns must implement match')
}
sampleTypes() {

View file

@ -2,7 +2,7 @@ import assert from 'assert'
import math from '#nanomath'
import {NumberT} from '#number/NumberT.js'
import {Returns} from '../Type.js'
import {Returns, ReturnTyping} from '../Type.js'
import {isPlainFunction} from '../helpers.js'
describe('Core types', () => {
@ -40,4 +40,7 @@ describe('Core types', () => {
assert(isPlainFunction(labeledF))
})
it('provides return typing strategies', () => {
assert.strictEqual(ReturnTyping.name(ReturnTyping.full), 'full')
})
})

View file

@ -6,7 +6,7 @@ import * as numbers from '#number/all.js'
import {NumberT} from '#number/NumberT.js'
import {ResolutionError} from "#core/helpers.js"
import {match, Any} from "#core/TypePatterns.js"
import {Returns, NotAType} from "#core/Type.js"
import {NotAType, Returns, ReturnTyping} from "#core/Type.js"
import {plain} from "#number/helpers.js"
describe('TypeDispatcher', () => {
@ -57,12 +57,15 @@ describe('TypeDispatcher', () => {
it('detects dependencies on conversion operations', () => {
const bgn = new TypeDispatcher(booleans, generics, numbers)
const {BooleanT, NumberT} = bgn.types
assert(!bgn._behaviors.negate.has([BooleanT]))
assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT]))
assert.strictEqual(bgn.negate(true), -1)
assert(bgn._behaviors.negate.has([BooleanT]))
const deps = bgn._dependencies.negate
assert(bgn._behaviors.negate.has([ReturnTyping.free, BooleanT]))
const deps = bgn._dependencies
.get('number')
.get([ReturnTyping.free, BooleanT])
assert(deps.has('negate'))
bgn.merge({number: match([BooleanT], Returns(NumberT, b => b ? 2 : 0))})
assert(!bgn._behaviors.negate.has([BooleanT]))
assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT]))
assert.strictEqual(bgn.negate(true), -2)
})
it('disallows merging NotAType', () => {

11
src/core/config.js Normal file
View file

@ -0,0 +1,11 @@
import {ImplementationsGenerator} from './Implementations.js'
import {ReturnTyping} from './Type.js'
import {match, Passthru} from './TypePatterns.js'
export const config = new ImplementationsGenerator(() => match(Passthru, {
// default comparison tolerances:
relTol: 1e-12,
absTol: 1e-15,
// Strategy for choosing operation return types:
returnTyping: ReturnTyping.free,
}))

View file

@ -1,3 +1,4 @@
import {config} from './config.js'
import {ImplementationsGenerator} from './Implementations.js'
import {Type, TypeOfTypes, Undefined, whichType} from './Type.js'
import {match, Passthru} from './TypePatterns.js'
@ -22,5 +23,5 @@ export const types = new ImplementationsGenerator(() => match(Passthru, {}))
// an explicitly ordered export of implementations for this sake:
export const bootstrapTypes = {
types, Type, Undefined, TypeOfTypes, typeOf
types, config, Type, Undefined, TypeOfTypes, typeOf
}

View file

@ -1,11 +1,18 @@
import assert from 'assert'
import math from '#nanomath'
import {ReturnTyping} from '#core/Type.js'
const {Complex, NumberT} = math.types
describe('generic arithmetic', () => {
it('squares anything', () => {
assert.strictEqual(math.square(7), 49)
assert.strictEqual(
math.square.resolve([math.types.NumberT]).returns,
math.types.NumberT)
const sq = math.square
assert.strictEqual(sq(7), 49)
assert.strictEqual(math.square.resolve([NumberT]).returns, NumberT)
assert.deepStrictEqual(sq(math.complex(3, 4)), math.complex(-7, 24))
const eyes = math.complex(0, 2)
assert.strictEqual(sq(eyes), -4)
const sqFull = math.square.resolve(Complex(NumberT), ReturnTyping.full)
assert.deepStrictEqual(sqFull(eyes), math.complex(-4, 0))
})
})

View file

@ -22,4 +22,7 @@ describe('generic utility functions', () => {
assert(isReal(math.complex(-3.25, 4e-16)))
assert(!isReal(math.complex(3, 4)))
})
it('tests for no imaginary part', () => {
assert(math.nonImaginary(true))
})
})

View file

@ -1,4 +1,3 @@
export * as arithmetic from './arithmetic.js'
export * as configuration from './config.js'
export * as relational from './relational.js'
export * as utilities from './utils.js'

View file

@ -1,8 +1,15 @@
import {Returns} from '#core/Type.js'
import {ReturnsAs} from './helpers.js'
import {Returns, ReturnTyping} from '#core/Type.js'
import {match, Any} from '#core/TypePatterns.js'
export const conj = match(Any, (_math, T) => Returns(T, a => a))
export const square = match(Any, (math, T) => {
const mult = math.multiply.resolve([T, T])
return Returns(mult.returns, a => mult(a, a))
export const abs = match(Any, (math, T) => {
const absq = math.absquare.resolve(T)
const sqrt = math.sqrt.resolve(absq.returns, ReturnTyping.conservative)
return ReturnsAs(sqrt, t => sqrt(absq(t)))
})
export const conj = match(Any, (_math, T) => Returns(T, t => t))
export const square = match(Any, (math, T, strategy) => {
const mult = math.multiply.resolve([T, T], strategy)
return Returns(mult.returns, t => mult(t, t))
})

View file

@ -1,5 +0,0 @@
import {ImplementationsGenerator} from '#core/Implementations.js'
import {match, Passthru} from '#core/TypePatterns.js'
export const config = new ImplementationsGenerator(
() => match(Passthru, {relTol: 1e-12, absTol: 1e-15}))

View file

@ -1,8 +1,10 @@
import {ReturnsAs} from './helpers.js'
import {Returns} from '#core/Type.js'
import {Returns, ReturnTyping} from '#core/Type.js'
import {Any, Passthru, match, matched} from '#core/TypePatterns.js'
import {boolnum} from '#number/helpers.js'
const {full} = ReturnTyping
export const equal = match([Any, Any], (math, [T, U]) => {
// Finding the correct signature of `indistinguishable` to use for
// testing (approximate) equality is tricky, because T or U might
@ -12,7 +14,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
// the matching type, and then we look up with tolerances.
let exactChecker
try {
exactChecker = math.indistinguishable.resolve([T, U])
exactChecker = math.indistinguishable.resolve([T, U], full)
} catch { // can't compare, so no way they can be equal
return boolnum(() => false)(math)
}
@ -25,7 +27,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
const {relTol, absTol} = typeConfig
const RT = math.typeOf(relTol)
const AT = math.typeOf(absTol)
const approx = math.indistinguishable.resolve([T, U, RT, AT])
const approx = math.indistinguishable.resolve([T, U, RT, AT], full)
return ReturnsAs(
approx, (t, u) => approx(t, u, relTol, absTol))
} catch {} // fall through to case with no tolerances
@ -86,6 +88,12 @@ export const larger = match([Any, Any], (math, [T, U]) => {
return boolnum((t, u) => !eq(t, u) && bigger(t, u))(math)
})
export const isPositive = match(Any, (math, T) => {
const zero = math.zero(T)
const larger = math.larger.resolve([T, T])
return boolnum(t => larger(t, zero))
})
export const largerEq = match([Any, Any], (math, [T, U]) => {
const eq = math.equal.resolve([T, U])
const bigger = math.exceeds.resolve([T, U])

View file

@ -1,10 +1,15 @@
import {ReturnsAs} from './helpers.js'
import {ResolutionError} from '#core/helpers.js'
import {Passthru, match} from '#core/TypePatterns.js'
import {Returns} from '#core/Type.js'
import {Any, Passthru, match} from '#core/TypePatterns.js'
import {boolnum} from '#number/helpers.js'
// Most types are real. Have to make sure to redefine on all non-real types
export const isReal = match(Passthru, boolnum(() => true))
// Most types are real, so we just define them that way generically.
// We have to make sure to redefine isReal on all non-real types.
// We use Any here so that it will match before a specific type matches
// with conversion; such a match runs the risk of not producing the correct
// result, or worse, leading to a resolution loop.
export const isReal = match(Any, boolnum(() => true))
export const isZero = match(Passthru, (math, [T]) => {
if (!T) { // called with no arguments
throw new ResolutionError('isZero() requires one argument')
@ -13,3 +18,9 @@ export const isZero = match(Passthru, (math, [T]) => {
const eq = math.equal.resolve([T, T])
return ReturnsAs(eq, x => eq(z, x))
})
export const nonImaginary = match(Passthru, boolnum(() => true))
export const re = match(Any, (_math, T) => Returns(T, t => t))
export const im = match(Any, (math, T) => {
const z = math.zero(T)
return Returns(T, () => z)
})

View file

@ -1,5 +1,6 @@
import assert from 'assert'
import math from '#nanomath'
import {ReturnTyping} from '#core/Type.js'
describe('number arithmetic', () => {
it('supports basic operations', () => {
@ -14,4 +15,18 @@ describe('number arithmetic', () => {
assert.strictEqual(math.subtract(4, 2), 2)
assert.strictEqual(math.quotient(7, 3), 2)
})
it('takes square root of numbers appropriately', () => {
assert(isNaN(math.sqrt(NaN)))
assert.strictEqual(math.sqrt(4), 2)
assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2))
math.config.returnTyping = ReturnTyping.conservative
assert(isNaN(math.sqrt(NaN)))
assert.strictEqual(math.sqrt(4), 2)
assert(isNaN(math.sqrt(-4)))
math.config.returnTyping = ReturnTyping.full
assert(isNaN(math.sqrt(NaN)))
assert.deepStrictEqual(math.sqrt(4), math.complex(2, 0))
assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2))
math.config.returnTyping = ReturnTyping.free
})
})

View file

@ -10,4 +10,11 @@ describe('number utilities', () => {
assert.strictEqual(math.isnan(Infinity), false)
assert.strictEqual(math.isnan(43), false)
})
it('tests if a number is an integer', () => {
assert(math.isInteger(7))
assert(math.isInteger(7+5e-16))
assert(!math.isInteger(7.000001))
assert(!math.isInteger(-Infinity))
assert(!math.isInteger(NaN))
})
})

View file

@ -1,4 +1,10 @@
import {plain} from './helpers.js'
import {NumberT} from './NumberT.js'
import {OneOf, Returns, ReturnTyping} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {Complex} from '#complex/Complex.js'
const {conservative, full} = ReturnTyping
export const abs = plain(Math.abs)
export const absquare = plain(a => a*a)
@ -18,5 +24,25 @@ export const cbrt = plain(a => {
export const invert = plain(a => 1/a)
export const multiply = plain((a, b) => a * b)
export const negate = plain(a => -a)
export const sqrt = match(NumberT, (math, _N, strategy) => {
if (!math.types.Complex || strategy === conservative) {
return Returns(NumberT, Math.sqrt)
}
const cplx = math.complex.resolve([NumberT, NumberT], full)
if (strategy === full) {
const cnan = math.nan(Complex(NumberT))
return Returns(Complex(NumberT), a => {
if (isNaN(a)) return cnan
return a >= 0 ? cplx(Math.sqrt(a), 0) : cplx(0, Math.sqrt(-a))
})
}
// strategy === free, return "best" type
return Returns(OneOf(NumberT, Complex(NumberT)), a => {
if (isNaN(a)) return NaN
return a >= 0 ? Math.sqrt(a) : cplx(0, Math.sqrt(-a))
})
})
export const subtract = plain((a, b) => a - b)
export const quotient = plain((a,b) => Math.floor(a/b))

View file

@ -1,4 +1,3 @@
import {Returns} from '#core/Type.js'
import {match, Optional} from '#core/TypePatterns.js'
import {boolnum} from './helpers.js'
import {NumberT} from './NumberT.js'

View file

@ -1,8 +1,13 @@
import {plain, boolnum} from './helpers.js'
import {NumberT} from './NumberT.js'
import {Returns} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
export const clone = plain(a => a)
export const isfinite = match(NumberT, boolnum(isFinite))
export const isInteger = match(NumberT, math => {
const finite = math.isfinite.resolve(NumberT)
const eq = math.equal.resolve([NumberT, NumberT])
return boolnum(a => finite(a) && eq(a, Math.round(a)))(math)
})
export const isnan = match(NumberT, boolnum(isNaN))

View file

@ -1,8 +1,9 @@
{
"imports" : {
"#nanomath": "./nanomath.js",
"#boolean/*.js": "./boolean/*.js",
"#core/*.js": "./core/*.js",
"#boolean/*.js": "./boolean/*.js",
"#complex/*.js": "./complex/*.js",
"#generic/*.js": "./generic/*.js",
"#number/*.js": "./number/*.js"
},