├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── examples │ ├── ana │ │ └── to-expr.ts │ ├── cata │ │ ├── natsum.ts │ │ └── show.ts │ └── histo │ │ └── coin-exchange.ts ├── schemes │ ├── ana.ts │ ├── cata.ts │ ├── futu.ts │ ├── histo.ts │ ├── hylo.ts │ └── para.ts └── types │ ├── Expr.ts │ ├── Nat.ts │ ├── algebras.ts │ ├── cofree.ts │ ├── fix.ts │ └── trampoline.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yuriy Bogomolov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Recursion Schemes Playground in TypeScript 2 | 3 | Playground for various recursion schemes done in TypeScript. 4 | 5 | ## How to run the examples 6 | 7 | The easiest way to play with the examples would be the following: 8 | 9 | 1. Fork, clone or just download the ZIP archive of this repo. 10 | 2. Run `npm ci` to install the dependencies. 11 | 3. Run `npm run `, where `` could be: 12 | 1. `ana:to-expr` – prints an expansion of a natural number to a combination of `+ 1`, `* 2`, `0`, `1` and `2`. 13 | 2. `cata:show` – pretty-prints an expression from example 1. 14 | 3. `cata:natsum` – converts a Peano number to a TS `number`. 15 | 4. `histo:coin-exchange` – solves a dynamic programming problem of coin exchange ("given a set of coins K and an amount M, in how many ways can you exchange M?"). 16 | 4. Change any inputs or try solving your own tasks using the provided schemes! 17 | 18 | ## Included schemes 19 | 20 | 1. Anamorphism: generalized unfold 21 | 2. Catamorphism: generalized fold 22 | 3. Hylomorphism: unfold followed by fold, recursively 23 | 4. Paramorphism: simple recursion which call tree is a cons-list 24 | 5. Histomorphism: fold with access to the history of previous computations 25 | 6. Futumorphism: unfold with possibility to choose future strategy of unfolding 26 | 27 | ## Credits 28 | 29 | 1. [Giulio Canti](https://github.com/gcanti) for his implementation of ana/cata/hylo/para for `fp-ts@1`: https://github.com/gcanti/recursion-schemes-ts 30 | 2. [Patrick Thomson](https://github.com/patrickt) for his incredible article series about recursion schemes: https://blog.sumtypeofway.com/posts/introduction-to-recursion-schemes.html 31 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-recursion-schemes-playground", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.5.5", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", 10 | "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", 11 | "requires": { 12 | "@babel/highlight": "^7.0.0" 13 | } 14 | }, 15 | "@babel/highlight": { 16 | "version": "7.5.0", 17 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", 18 | "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", 19 | "requires": { 20 | "chalk": "^2.0.0", 21 | "esutils": "^2.0.2", 22 | "js-tokens": "^4.0.0" 23 | } 24 | }, 25 | "@types/node": { 26 | "version": "12.12.17", 27 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.17.tgz", 28 | "integrity": "sha512-Is+l3mcHvs47sKy+afn2O1rV4ldZFU7W8101cNlOd+MRbjM4Onida8jSZnJdTe/0Pcf25g9BNIUsuugmE6puHA==" 29 | }, 30 | "ansi-styles": { 31 | "version": "3.2.1", 32 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 33 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 34 | "requires": { 35 | "color-convert": "^1.9.0" 36 | } 37 | }, 38 | "arg": { 39 | "version": "4.1.2", 40 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.2.tgz", 41 | "integrity": "sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg==" 42 | }, 43 | "argparse": { 44 | "version": "1.0.10", 45 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 46 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 47 | "requires": { 48 | "sprintf-js": "~1.0.2" 49 | } 50 | }, 51 | "balanced-match": { 52 | "version": "1.0.0", 53 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 54 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 55 | }, 56 | "brace-expansion": { 57 | "version": "1.1.11", 58 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 59 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 60 | "requires": { 61 | "balanced-match": "^1.0.0", 62 | "concat-map": "0.0.1" 63 | } 64 | }, 65 | "buffer-from": { 66 | "version": "1.1.1", 67 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 68 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 69 | }, 70 | "builtin-modules": { 71 | "version": "1.1.1", 72 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 73 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" 74 | }, 75 | "chalk": { 76 | "version": "2.4.2", 77 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 78 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 79 | "requires": { 80 | "ansi-styles": "^3.2.1", 81 | "escape-string-regexp": "^1.0.5", 82 | "supports-color": "^5.3.0" 83 | } 84 | }, 85 | "color-convert": { 86 | "version": "1.9.3", 87 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 88 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 89 | "requires": { 90 | "color-name": "1.1.3" 91 | } 92 | }, 93 | "color-name": { 94 | "version": "1.1.3", 95 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 96 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 97 | }, 98 | "commander": { 99 | "version": "2.20.3", 100 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 101 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 102 | }, 103 | "concat-map": { 104 | "version": "0.0.1", 105 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 106 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 107 | }, 108 | "diff": { 109 | "version": "4.0.1", 110 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", 111 | "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==" 112 | }, 113 | "escape-string-regexp": { 114 | "version": "1.0.5", 115 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 116 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 117 | }, 118 | "esprima": { 119 | "version": "4.0.1", 120 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 121 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 122 | }, 123 | "esutils": { 124 | "version": "2.0.3", 125 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 126 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" 127 | }, 128 | "fp-ts": { 129 | "version": "2.3.1", 130 | "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.3.1.tgz", 131 | "integrity": "sha512-KevPBnYt0aaJiuUzmU9YIxjrhC9AgJ8CLtLlXmwArovlNTeYM5NtEoKd86B0wHd7FIbzeE8sNXzCoYIOr7e6Iw==" 132 | }, 133 | "fp-ts-contrib": { 134 | "version": "0.1.8", 135 | "resolved": "https://registry.npmjs.org/fp-ts-contrib/-/fp-ts-contrib-0.1.8.tgz", 136 | "integrity": "sha512-4BCshD2SdXoRrq9lyR6bMDuLCqwToh5RWgX919gjjPBXJCTiRfmD972hSxHRyZFqTjU8pd17sdhj7odJkfgvUg==" 137 | }, 138 | "fs.realpath": { 139 | "version": "1.0.0", 140 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 141 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 142 | }, 143 | "glob": { 144 | "version": "7.1.6", 145 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 146 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 147 | "requires": { 148 | "fs.realpath": "^1.0.0", 149 | "inflight": "^1.0.4", 150 | "inherits": "2", 151 | "minimatch": "^3.0.4", 152 | "once": "^1.3.0", 153 | "path-is-absolute": "^1.0.0" 154 | } 155 | }, 156 | "has-flag": { 157 | "version": "3.0.0", 158 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 159 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 160 | }, 161 | "inflight": { 162 | "version": "1.0.6", 163 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 164 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 165 | "requires": { 166 | "once": "^1.3.0", 167 | "wrappy": "1" 168 | } 169 | }, 170 | "inherits": { 171 | "version": "2.0.4", 172 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 173 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 174 | }, 175 | "js-tokens": { 176 | "version": "4.0.0", 177 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 178 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 179 | }, 180 | "js-yaml": { 181 | "version": "3.13.1", 182 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 183 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 184 | "requires": { 185 | "argparse": "^1.0.7", 186 | "esprima": "^4.0.0" 187 | } 188 | }, 189 | "make-error": { 190 | "version": "1.3.5", 191 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", 192 | "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" 193 | }, 194 | "minimatch": { 195 | "version": "3.0.4", 196 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 197 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 198 | "requires": { 199 | "brace-expansion": "^1.1.7" 200 | } 201 | }, 202 | "minimist": { 203 | "version": "0.0.8", 204 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 205 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 206 | }, 207 | "mkdirp": { 208 | "version": "0.5.1", 209 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 210 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 211 | "requires": { 212 | "minimist": "0.0.8" 213 | } 214 | }, 215 | "once": { 216 | "version": "1.4.0", 217 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 218 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 219 | "requires": { 220 | "wrappy": "1" 221 | } 222 | }, 223 | "path-is-absolute": { 224 | "version": "1.0.1", 225 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 226 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 227 | }, 228 | "path-parse": { 229 | "version": "1.0.7", 230 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 231 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 232 | }, 233 | "resolve": { 234 | "version": "1.13.1", 235 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", 236 | "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", 237 | "requires": { 238 | "path-parse": "^1.0.6" 239 | } 240 | }, 241 | "semver": { 242 | "version": "5.7.1", 243 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 244 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 245 | }, 246 | "source-map": { 247 | "version": "0.6.1", 248 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 249 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 250 | }, 251 | "source-map-support": { 252 | "version": "0.5.16", 253 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", 254 | "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", 255 | "requires": { 256 | "buffer-from": "^1.0.0", 257 | "source-map": "^0.6.0" 258 | } 259 | }, 260 | "sprintf-js": { 261 | "version": "1.0.3", 262 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 263 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" 264 | }, 265 | "supports-color": { 266 | "version": "5.5.0", 267 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 268 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 269 | "requires": { 270 | "has-flag": "^3.0.0" 271 | } 272 | }, 273 | "ts-node": { 274 | "version": "8.5.4", 275 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.4.tgz", 276 | "integrity": "sha512-izbVCRV68EasEPQ8MSIGBNK9dc/4sYJJKYA+IarMQct1RtEot6Xp0bXuClsbUSnKpg50ho+aOAx8en5c+y4OFw==", 277 | "requires": { 278 | "arg": "^4.1.0", 279 | "diff": "^4.0.1", 280 | "make-error": "^1.1.1", 281 | "source-map-support": "^0.5.6", 282 | "yn": "^3.0.0" 283 | } 284 | }, 285 | "tslib": { 286 | "version": "1.10.0", 287 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 288 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" 289 | }, 290 | "tslint": { 291 | "version": "5.20.1", 292 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", 293 | "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", 294 | "requires": { 295 | "@babel/code-frame": "^7.0.0", 296 | "builtin-modules": "^1.1.1", 297 | "chalk": "^2.3.0", 298 | "commander": "^2.12.1", 299 | "diff": "^4.0.1", 300 | "glob": "^7.1.1", 301 | "js-yaml": "^3.13.1", 302 | "minimatch": "^3.0.4", 303 | "mkdirp": "^0.5.1", 304 | "resolve": "^1.3.2", 305 | "semver": "^5.3.0", 306 | "tslib": "^1.8.0", 307 | "tsutils": "^2.29.0" 308 | } 309 | }, 310 | "tsutils": { 311 | "version": "2.29.0", 312 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 313 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 314 | "requires": { 315 | "tslib": "^1.8.1" 316 | } 317 | }, 318 | "typescript": { 319 | "version": "3.7.3", 320 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", 321 | "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==" 322 | }, 323 | "wrappy": { 324 | "version": "1.0.2", 325 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 326 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 327 | }, 328 | "yn": { 329 | "version": "3.1.1", 330 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 331 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" 332 | } 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-recursion-schemes-playground", 3 | "version": "1.0.0", 4 | "description": "TypeScript playground for recursion schemes", 5 | "main": "index.js", 6 | "scripts": { 7 | "ana:to-expr": "ts-node ./src/examples/ana/to-expr.ts", 8 | "cata:natsum": "ts-node ./src/examples/cata/natsum.ts", 9 | "cata:show": "ts-node ./src/examples/cata/show.ts", 10 | "histo:coin-exchange": "ts-node ./src/examples/histo/coin-exchange.ts" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/YBogomolov/ts-recursion-schemes-playground.git" 15 | }, 16 | "keywords": [ 17 | "typescript", 18 | "recursion", 19 | "schemes", 20 | "ts", 21 | "fp" 22 | ], 23 | "author": "Yuriy Bogomolov ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/YBogomolov/ts-recursion-schemes-playground/issues" 27 | }, 28 | "homepage": "https://github.com/YBogomolov/ts-recursion-schemes-playground#readme", 29 | "dependencies": { 30 | "@types/node": "12.12.17", 31 | "fp-ts": "2.3.1", 32 | "fp-ts-contrib": "0.1.8", 33 | "ts-node": "8.5.4", 34 | "tslint": "5.20.1", 35 | "typescript": "3.7.3" 36 | } 37 | } -------------------------------------------------------------------------------- /src/examples/ana/to-expr.ts: -------------------------------------------------------------------------------- 1 | import { ana } from '../../schemes/ana'; 2 | import { cata } from '../../schemes/cata'; 3 | import { Add, Const, Expr, ExprF, functorExpr, Mul } from '../../types/Expr'; 4 | 5 | function show(expr: Expr): string { 6 | function alg(ex: ExprF): string { 7 | switch (ex._tag) { 8 | case 'Const': return `${ex.value}`; 9 | case 'Add': return `(${ex.x}+${ex.y})`; 10 | case 'Mul': return `(${ex.x}*${ex.y})`; 11 | } 12 | } 13 | 14 | return cata(functorExpr)(alg)(expr); 15 | } 16 | 17 | function toExpr(n: number): Expr { 18 | function coalg(a: number): ExprF { 19 | // poor man's pattern matching: 20 | switch (true) { 21 | case a === 0: return new Const(0); 22 | case a === 1: return new Const(1); 23 | case a === 2: return new Const(2); 24 | case a % 2 === 0: return new Mul(2, a / 2); 25 | default: return new Add(1, a - 1); 26 | } 27 | } 28 | 29 | return ana(functorExpr)(coalg)(n); 30 | } 31 | 32 | console.log(show(toExpr(42))); // > (2*(1+(2*(2*(1+(2*2)))))) 33 | -------------------------------------------------------------------------------- /src/examples/cata/natsum.ts: -------------------------------------------------------------------------------- 1 | import { cata } from '../../schemes/cata'; 2 | import { functorNat, Nat, NatF, succ, zero } from '../../types/Nat'; 3 | 4 | export function natsum(nat: Nat): number { 5 | function alg(n: NatF): number { 6 | switch (n._tag) { 7 | case 'Zero': return 0; 8 | case 'Succ': return 1 + n.value; 9 | } 10 | } 11 | 12 | return cata(functorNat)(alg)(nat); 13 | } 14 | 15 | console.log(natsum(succ(succ(succ(succ(zero)))))); 16 | -------------------------------------------------------------------------------- /src/examples/cata/show.ts: -------------------------------------------------------------------------------- 1 | import { cata } from '../../schemes/cata'; 2 | import { add, const_, Expr, ExprF, functorExpr, mul } from '../../types/Expr'; 3 | 4 | export function show(expr: Expr): string { 5 | function alg(ex: ExprF): string { 6 | switch (ex._tag) { 7 | case 'Const': return `${ex.value}`; 8 | case 'Add': return `(${ex.x}+${ex.y})`; 9 | case 'Mul': return `(${ex.x}*${ex.y})`; 10 | } 11 | } 12 | 13 | return cata(functorExpr)(alg)(expr); 14 | } 15 | 16 | console.log(show(mul(add(const_(1), const_(2)), const_(3)))); // => ((1+2)*3) 17 | -------------------------------------------------------------------------------- /src/examples/histo/coin-exchange.ts: -------------------------------------------------------------------------------- 1 | import { partition } from 'fp-ts/lib/Array'; 2 | import { pipe } from 'fp-ts/lib/pipeable'; 3 | 4 | import { histo } from '../../schemes/histo'; 5 | import { attr, Cofree, hole } from '../../types/cofree'; 6 | import { functorNat, Nat, NatF, succ, URI, zero } from '../../types/Nat'; 7 | 8 | type NatCofree = Cofree; 9 | 10 | const AVAILABLE_COINS = [50, 25, 10, 5, 1]; 11 | 12 | const expand = (amount: number): Nat => amount === 0 ? zero : succ(expand(amount - 1)); 13 | 14 | const compress = (n: NatF): number => { 15 | switch (n._tag) { 16 | case 'Zero': return 0; 17 | case 'Succ': return 1 + compress(hole(n.value)); 18 | } 19 | }; 20 | 21 | const lookup = (cache: Cofree, n: number): A => 22 | n === 0 ? attr(cache) : lookup(hole(cache).value, n - 1); 23 | 24 | const exchange = (amount: number): number[][] => { 25 | function go(curr: NatF): number[][] { 26 | switch (curr._tag) { 27 | case 'Zero': return [[]]; 28 | case 'Succ': { 29 | const given = compress(curr); 30 | const validCoins = AVAILABLE_COINS.filter((coin) => coin <= given); 31 | const remaining = validCoins.map((coin) => [coin, given - coin]); 32 | const { right: zeroes, left: toProcess } = pipe(remaining, partition((a) => a[1] === 0)); 33 | const results = toProcess.flatMap( 34 | ([coin, remainder]) => lookup(curr.value, given - 1 - remainder) 35 | .filter((cs) => cs.every((c) => c <= coin)) 36 | .map((cs) => [coin, ...cs]), 37 | ); 38 | 39 | return zeroes.map(([coin, _remainder]) => [coin]).concat(results); 40 | } 41 | } 42 | } 43 | 44 | return histo(functorNat)(go)(expand(amount)); 45 | }; 46 | 47 | console.log(exchange(25)); // => 48 | // [ 49 | // [25], 50 | // [10, 10, 5], 51 | // [10, 10, 1, 1, 1, 1, 1], 52 | // [10, 5, 5, 5], 53 | // [10, 5, 5, 1, 1, 1, 1, 1], 54 | // [10, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 55 | // [10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 56 | // [5, 5, 5, 5, 5], 57 | // [5, 5, 5, 5, 1, 1, 1, 1, 1], 58 | // [5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 59 | // [5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 60 | // [5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 61 | // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 62 | // ] 63 | -------------------------------------------------------------------------------- /src/schemes/ana.ts: -------------------------------------------------------------------------------- 1 | import { flow } from 'fp-ts/lib/function'; 2 | import { Functor, Functor1, Functor2, Functor3 } from 'fp-ts/lib/Functor'; 3 | import { HKT, Kind, Kind2, Kind3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'; 4 | 5 | import { Coalgebra } from '../types/algebras'; 6 | import { Fix, fix } from '../types/fix'; 7 | 8 | // tslint:disable:max-line-length 9 | /** anamorphism - builds up a structure level by level */ 10 | export function ana(F: Functor3): (coalgebra: (a: A) => Kind3) => (a: A) => Fix; 11 | export function ana(F: Functor2): (coalgebra: (a: A) => Kind2) => (a: A) => Fix; 12 | export function ana(F: Functor1): (coalgebra: (a: A) => Kind) => (a: A) => Fix; 13 | export function ana(F: Functor): (coalgebra: Coalgebra) => (a: A) => Fix; 14 | export function ana(F: Functor): (coalgebra: Coalgebra) => (a: A) => Fix { 15 | return (coalgebra) => flow(coalgebra, (x) => F.map(x, ana(F)(coalgebra)), fix); 16 | } 17 | -------------------------------------------------------------------------------- /src/schemes/cata.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-line-length 2 | import { Functor, Functor1, Functor2, Functor3 } from 'fp-ts/lib/Functor'; 3 | import { Kind, Kind2, Kind3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'; 4 | 5 | import { Algebra } from '../types/algebras'; 6 | import { Fix, unfix } from '../types/fix'; 7 | 8 | /** catamorphism - tears down a structure level by level */ 9 | export function cata(F: Functor3, ): (algebra: (fa: Kind3) => A) => (term: Fix) => A; 10 | export function cata(F: Functor2): (algebra: (fa: Kind2) => A) => (term: Fix) => A; 11 | export function cata(F: Functor1): (algebra: (fa: Kind) => A) => (term: Fix) => A; 12 | export function cata(F: Functor): (algebra: Algebra) => (term: Fix) => A; 13 | export function cata(F: Functor): (algebra: Algebra) => (term: Fix) => A { 14 | return (algebra: Algebra) => 15 | function self(term): A { 16 | return algebra(F.map(unfix(term), self)); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/schemes/futu.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-line-length 2 | import { Free } from 'fp-ts-contrib/lib/Free'; 3 | import { Functor, Functor1, Functor2, Functor3 } from 'fp-ts/lib/Functor'; 4 | import { Kind, Kind2, Kind3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'; 5 | 6 | import { CVCoalgebra } from '../types/algebras'; 7 | import { Fix, fix } from '../types/fix'; 8 | 9 | export function futu(F: Functor3): (coalgebra: (a: A) => Kind3>) => (a: A) => Fix; 10 | export function futu(F: Functor2): (coalgebra: (a: A) => Kind2>) => (a: A) => Fix; 11 | export function futu(F: Functor1): (coalgebra: (a: A) => Kind>) => (a: A) => Fix; 12 | export function futu(F: Functor): (coalgebra: CVCoalgebra) => (a: A) => Fix; 13 | export function futu(F: Functor): (coalgebra: CVCoalgebra) => (a: A) => Fix { 14 | return (coalgebra: CVCoalgebra) => { 15 | return function self(a: A): Fix { 16 | const worker = (t: Free): Fix => { 17 | switch (t._tag) { 18 | case 'Pure': return self(t.value); 19 | case 'Impure': return fix(F.map(t.fx, worker)); 20 | } 21 | }; 22 | return fix(F.map(coalgebra(a), worker)); 23 | }; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/schemes/histo.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-line-length 2 | import { Functor, Functor1, Functor2, Functor3 } from 'fp-ts/lib/Functor'; 3 | import { Kind, Kind2, Kind3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'; 4 | 5 | import { CVAlgebra } from '../types/algebras'; 6 | import { attr, Cofree, cofree } from '../types/cofree'; 7 | import { Fix, unfix } from '../types/fix'; 8 | 9 | export function histo(F: Functor3): (algebra: (fa: Kind3>) => A) => (term: Fix) => A; 10 | export function histo(F: Functor2): (algebra: (fa: Kind2>) => A) => (term: Fix) => A; 11 | export function histo(F: Functor1): (algebra: (fa: Kind>) => A) => (term: Fix) => A; 12 | export function histo(F: Functor): (algebra: CVAlgebra) => (term: Fix) => A; 13 | export function histo(F: Functor): (algebra: CVAlgebra) => (term: Fix) => A { 14 | return (algebra: CVAlgebra) => { 15 | return function self(term): A { 16 | const worker = (t: Fix): Cofree => { 17 | const calc = F.map(unfix(t), worker); 18 | return cofree(algebra(calc), () => calc); 19 | }; 20 | return attr(worker(term)); 21 | }; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/schemes/hylo.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-line-length 2 | import { compose } from 'fp-ts/lib/function'; 3 | import { Functor, Functor1, Functor2, Functor3 } from 'fp-ts/lib/Functor'; 4 | import { Type, Type2, Type3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'; 5 | 6 | import { Algebra, Coalgebra } from './algebra'; 7 | 8 | export function hylo(F: Functor3): (algebra: (fa: Type3) => B, coalgebra: (a: A) => Type3) => (a: A) => B; 9 | export function hylo(F: Functor2): (algebra: (fa: Type2) => B, coalgebra: (a: A) => Type2) => (a: A) => B; 10 | export function hylo(F: Functor1): (algebra: (fa: Type) => B, coalgebra: (a: A) => Type) => (a: A) => B; 11 | export function hylo(F: Functor): (algebra: Algebra, coalgebra: Coalgebra) => (a: A) => B; 12 | export function hylo(F: Functor): (algebra: Algebra, coalgebra: Coalgebra) => (a: A) => B { 13 | return (algebra, coalgebra) => compose(algebra, (x) => F.map(x, hylo(F)(algebra, coalgebra)), coalgebra); 14 | } 15 | -------------------------------------------------------------------------------- /src/schemes/para.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-line-length 2 | import { flow } from 'fp-ts/lib/function'; 3 | import { Functor, Functor1, Functor2, Functor3 } from 'fp-ts/lib/Functor'; 4 | import { HKT, Kind, Kind2, Kind3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'; 5 | 6 | import { RAlgebra } from '../types/algebras'; 7 | import { Fix, unfix } from '../types/fix'; 8 | 9 | /** paramorphism - tears down a structure with primitive recursion */ 10 | export function para(F: Functor3): (ralgebra: (t: Kind3, A]>) => A) => (term: Fix) => A; 11 | export function para(F: Functor2): (ralgebra: (t: Kind2, A]>) => A) => (term: Fix) => A; 12 | export function para(F: Functor1): (ralgebra: (t: Kind, A]>) => A) => (term: Fix) => A; 13 | export function para(F: Functor): (ralgebra: RAlgebra) => (term: Fix) => A; 14 | export function para(F: Functor): (ralgebra: RAlgebra) => (term: Fix) => A { 15 | return (ralgebra: RAlgebra) => { 16 | function fanout(term: Fix): [Fix, A] { 17 | return [term, para(F)(ralgebra)(term)]; 18 | } 19 | return flow(unfix, (x) => F.map(x, fanout), ralgebra); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/types/Expr.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-classes-per-file 2 | // tslint:disable:variable-name 3 | 4 | import { unsafeCoerce } from 'fp-ts/lib/function'; 5 | import { Functor1 } from 'fp-ts/lib/Functor'; 6 | 7 | import { Fix, fix } from './fix'; 8 | 9 | declare module 'fp-ts/lib/HKT' { 10 | interface URItoKind { 11 | Expr: ExprF; 12 | } 13 | } 14 | 15 | export const URI = 'Expr'; 16 | export type URI = typeof URI; 17 | 18 | export class Const { 19 | readonly _tag: 'Const' = 'Const'; 20 | readonly '_A'!: A; 21 | readonly '_URI'!: URI; 22 | constructor(public value: number) { } 23 | } 24 | 25 | export class Add { 26 | readonly _tag: 'Add' = 'Add'; 27 | readonly '_A'!: A; 28 | readonly '_URI'!: URI; 29 | constructor(public x: A, public y: A) { } 30 | } 31 | 32 | export class Mul { 33 | readonly _tag: 'Mul' = 'Mul'; 34 | readonly '_A'!: A; 35 | readonly '_URI'!: URI; 36 | constructor(public x: A, public y: A) { } 37 | } 38 | 39 | // Simple arithmetic expression, consisting of constants, additions or multiplications 40 | export type ExprF = Const | Add | Mul; 41 | 42 | export type Expr = Fix; 43 | 44 | // Constructors 45 | 46 | export function const_(n: number): Expr { 47 | return fix(new Const(n)); 48 | } 49 | 50 | export function add(x: Expr, y: Expr): Expr { 51 | return fix(new Add(x, y)); 52 | } 53 | 54 | export function mul(x: Expr, y: Expr): Expr { 55 | return fix(new Mul(x, y)); 56 | } 57 | 58 | // Functor instance 59 | 60 | export const map = (expr: ExprF, f: (a: A) => B): ExprF => { 61 | switch (expr._tag) { 62 | case 'Const': return unsafeCoerce(expr); 63 | case 'Add': return new Add(f(expr.x), f(expr.y)); 64 | case 'Mul': return new Mul(f(expr.x), f(expr.y)); 65 | } 66 | }; 67 | 68 | export const functorExpr: Functor1 = { 69 | URI, 70 | map, 71 | }; 72 | -------------------------------------------------------------------------------- /src/types/Nat.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-classes-per-file 2 | // tslint:disable:variable-name 3 | // tslint:disable:no-any 4 | 5 | import { unsafeCoerce } from 'fp-ts/lib/function'; 6 | import { Functor1 } from 'fp-ts/lib/Functor'; 7 | 8 | import { Fix, fix } from './fix'; 9 | 10 | declare module 'fp-ts/lib/HKT' { 11 | interface URItoKind { 12 | Nat: NatF; 13 | } 14 | } 15 | 16 | export const URI = 'Nat'; 17 | export type URI = typeof URI; 18 | 19 | class Zero { 20 | static value = new Zero(); 21 | public value!: never; 22 | readonly _tag: 'Zero' = 'Zero'; 23 | readonly '_A'!: A; 24 | readonly '_URI'!: URI; 25 | private constructor() { } 26 | } 27 | 28 | class Succ { 29 | readonly _tag: 'Succ' = 'Succ'; 30 | readonly '_A'!: A; 31 | readonly '_URI'!: URI; 32 | constructor(public value: A) { } 33 | } 34 | 35 | /** 36 | * Peano-encoded natural numbers: either Zero or a number next to a natural number (Succ) 37 | */ 38 | export type NatF = Zero | Succ; 39 | 40 | export type Nat = Fix; 41 | 42 | // Constructors 43 | 44 | export const zero = fix(Zero.value); 45 | export const succ = (n: Nat): Nat => fix(new Succ(n)); 46 | 47 | // Functor instance 48 | 49 | export const map = (nat: NatF, f: (a: A) => B): NatF => { 50 | switch (nat._tag) { 51 | case 'Zero': return unsafeCoerce(nat); 52 | case 'Succ': return new Succ(f(nat.value)); 53 | } 54 | }; 55 | 56 | export const functorNat: Functor1 = { 57 | URI, 58 | map, 59 | }; 60 | -------------------------------------------------------------------------------- /src/types/algebras.ts: -------------------------------------------------------------------------------- 1 | import { Free } from 'fp-ts-contrib/lib/Free'; 2 | import { HKT } from 'fp-ts/lib/HKT'; 3 | 4 | import { Cofree } from './cofree'; 5 | import { Fix } from './fix'; 6 | 7 | // Algebra: take `F` and produce `A` 8 | export type Algebra = (fa: HKT) => A; 9 | 10 | // Coalgebra: take `A` and produce `F` 11 | export type Coalgebra = (a: A) => HKT; 12 | 13 | // R-algebra: take `F` and extract `A` 14 | export type RAlgebra = (t: HKT, A]>) => A; 15 | 16 | // Course-of-value algebra: take `F>>` and extract `A` 17 | export type CVAlgebra = (fa: HKT>) => A; 18 | 19 | // Course-of-value coalgebra: take `A` and generate `F>>` 20 | export type CVCoalgebra = (a: A) => HKT>; 21 | -------------------------------------------------------------------------------- /src/types/cofree.ts: -------------------------------------------------------------------------------- 1 | import { Lazy } from 'fp-ts/lib/function'; 2 | import { HKT, Kind, URIS } from 'fp-ts/lib/HKT'; 3 | 4 | import { Trampoline, trampoline } from './trampoline'; 5 | 6 | /** 7 | * Cofree – a stream/tree-like structure consisting of a concrete value of type `A` and 8 | * further computations of type `F>>`. 9 | * 10 | * Value of type `A` is usually called `attribute`, 11 | * and next computations are called `hole`. 12 | */ 13 | export class Cofree { 14 | // Normally in Cofree you would want something like `Eval` monad. 15 | // @see https://typelevel.org/cats/datatypes/eval.html for details 16 | // Port of `Eval` to TypeScript is quite straightforward, but rather lengthy, so for brewity I'll leave `Lazy` here: 17 | constructor(readonly attribute: A, readonly rest: Trampoline>>) { } 18 | } 19 | 20 | // Constuctor 21 | export const cofree = (a: A, h: Lazy>>) => new Cofree(a, h); 22 | 23 | // Extract attribute from the cofree structure 24 | export const attr = (c: Cofree): A => c.attribute; 25 | 26 | // Exctract hole from the cofree structure 27 | export function hole(c: Cofree): Kind>; 28 | export function hole(c: Cofree): HKT> { 29 | return trampoline(c.rest); 30 | } 31 | -------------------------------------------------------------------------------- /src/types/fix.ts: -------------------------------------------------------------------------------- 1 | import { HKT } from 'fp-ts/lib/HKT'; 2 | 3 | /** 4 | * Fixpoint of type `F` 5 | * @see https://en.wikibooks.org/wiki/Haskell/Fix_and_recursion for more details 6 | */ 7 | export class Fix { 8 | constructor(public readonly value: HKT>) { } 9 | } 10 | 11 | export const fix = (value: HKT>): Fix => new Fix(value); 12 | export const unfix = (term: Fix): HKT> => term.value; 13 | -------------------------------------------------------------------------------- /src/types/trampoline.ts: -------------------------------------------------------------------------------- 1 | export type Trampoline = T | (() => Trampoline); 2 | 3 | export function trampoline(firstResult: Trampoline) { 4 | let result = firstResult; 5 | while (result instanceof Function) { 6 | result = result(); 7 | } 8 | return result; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "allowJs": false, 6 | "checkJs": false, 7 | "rootDir": "src/**.ts", 8 | "downlevelIteration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "strictBindCallApply": true, 14 | "strictPropertyInitialization": true, 15 | "noImplicitThis": true, 16 | "alwaysStrict": true, 17 | "moduleResolution": "node", 18 | "types": [ 19 | "node" 20 | ], 21 | "esModuleInterop": true, 22 | "forceConsistentCasingInFileNames": true 23 | } 24 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended" 4 | ], 5 | "defaultSeverity": "error", 6 | "rules": { 7 | "no-console": [ 8 | false 9 | ], 10 | "quotemark": [ 11 | true, 12 | "single" 13 | ], 14 | "ordered-imports": [ 15 | true, 16 | { 17 | "import-sources-order": "case-insensitive", 18 | "grouped-imports": true, 19 | "groups": [ 20 | { 21 | "name": "relative", 22 | "match": "^\\.\\./", 23 | "order": 30 24 | }, 25 | { 26 | "name": "local", 27 | "match": "(^\\./)|(^\\.$)", 28 | "order": 40 29 | }, 30 | { 31 | "name": "modules", 32 | "match": ".*", 33 | "order": 20 34 | } 35 | ] 36 | } 37 | ], 38 | "member-ordering": [ 39 | false 40 | ], 41 | "object-literal-sort-keys": [ 42 | false 43 | ], 44 | "trailing-comma": [ 45 | true, 46 | { 47 | "multiline": { 48 | "objects": "always", 49 | "arrays": "always", 50 | "functions": "always", 51 | "typeLiterals": "ignore" 52 | }, 53 | "esSpecCompliant": true 54 | } 55 | ], 56 | "indent": [ 57 | true, 58 | "spaces" 59 | ], 60 | "interface-name": [ 61 | true, 62 | "never-prefix" 63 | ], 64 | "max-line-length": [ 65 | true, 66 | 120 67 | ], 68 | "semicolon": [ 69 | true, 70 | "always" 71 | ], 72 | "object-literal-key-quotes": [ 73 | true, 74 | "as-needed" 75 | ], 76 | "linebreak-style": [ 77 | true, 78 | "LF" 79 | ], 80 | "whitespace": [ 81 | true, 82 | "check-branch", 83 | "check-decl", 84 | "check-operator", 85 | "check-module", 86 | "check-separator", 87 | "check-rest-spread", 88 | "check-type", 89 | "check-typecast", 90 | "check-type-operator", 91 | "check-preblock" 92 | ], 93 | "member-access": false, 94 | "no-any": true, 95 | "variable-name": false, 96 | "jsx-no-multiline-js": false, 97 | "no-implicit-dependencies": [ 98 | true, 99 | "dev" 100 | ], 101 | "max-classes-per-file": false 102 | } 103 | } --------------------------------------------------------------------------------