├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── 01-nullable-values │ └── nullable-values.test.ts ├── 02-handling-errors │ └── handling-errors.test.ts ├── 03-async │ └── async.test.ts ├── lib │ ├── env.ts │ └── errors.ts ├── middleware │ ├── chain-of-responsibility.ts │ ├── middleware.test.ts │ ├── middleware.ts │ ├── promises.ts │ └── sweets.ts ├── pointfree │ ├── composing.test.ts │ └── composing.ts └── unknown_changes │ ├── io.test.ts │ └── io.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.js 3 | *.js.map 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exploring-fp-ts-series 2 | 3 | ## Note: This code is based on `fp-ts` version 1 4 | For guides on how to use version 2 please refer to the [`fp-ts` website](https://gcanti.github.io/fp-ts/) 5 | 6 | Supporting code for the Exploring fp-ts series. 7 | 8 | The intro can be found here: 9 | 10 | https://davetayls.me/blog/2018/05/19/fp-ts-00-exploring-fp-ts-series 11 | 12 | ## Usage 13 | 14 | Install the node dependencies 15 | 16 | ``` 17 | npm install 18 | ``` 19 | 20 | All the examples are inside the tests which can be run 21 | 22 | ``` 23 | npm test 24 | ``` 25 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exploring-fp-ts-series", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@sinonjs/formatio": { 8 | "version": "2.0.0", 9 | "resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", 10 | "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", 11 | "requires": { 12 | "samsam": "1.3.0" 13 | } 14 | }, 15 | "@types/mocha": { 16 | "version": "5.2.0", 17 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.0.tgz", 18 | "integrity": "sha512-YeDiSEzznwZwwp766SJ6QlrTyBYUGPSIwmREHVTmktUYiT/WADdWtpt9iH0KuUSf8lZLdI4lP0X6PBzPo5//JQ==" 19 | }, 20 | "@types/node": { 21 | "version": "10.0.4", 22 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.0.4.tgz", 23 | "integrity": "sha512-RisaZmcmCLjRipAY7nVi3fmkIk4Z0JMn8YHdGF6qYMsIDpD0dfzz+3yy2dL5Q5aHWOnqPx51IRxkA44myknJvw==" 24 | }, 25 | "@types/sinon": { 26 | "version": "4.3.1", 27 | "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.1.tgz", 28 | "integrity": "sha512-DK4YtH30I67k4klURIBS4VAe1aBISfS9lgNlHFkibSmKem2tLQc5VkKoJreT3dCJAd+xRyCS8bx1o97iq3yUVg==" 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.1" 36 | } 37 | }, 38 | "arrify": { 39 | "version": "1.0.1", 40 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 41 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" 42 | }, 43 | "balanced-match": { 44 | "version": "1.0.0", 45 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 46 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 47 | }, 48 | "brace-expansion": { 49 | "version": "1.1.11", 50 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 51 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 52 | "requires": { 53 | "balanced-match": "1.0.0", 54 | "concat-map": "0.0.1" 55 | } 56 | }, 57 | "browser-stdout": { 58 | "version": "1.3.1", 59 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 60 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" 61 | }, 62 | "buffer-from": { 63 | "version": "1.0.0", 64 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", 65 | "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" 66 | }, 67 | "chalk": { 68 | "version": "2.4.1", 69 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 70 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 71 | "requires": { 72 | "ansi-styles": "3.2.1", 73 | "escape-string-regexp": "1.0.5", 74 | "supports-color": "5.4.0" 75 | }, 76 | "dependencies": { 77 | "has-flag": { 78 | "version": "3.0.0", 79 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 80 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 81 | }, 82 | "supports-color": { 83 | "version": "5.4.0", 84 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 85 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 86 | "requires": { 87 | "has-flag": "3.0.0" 88 | } 89 | } 90 | } 91 | }, 92 | "color-convert": { 93 | "version": "1.9.1", 94 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", 95 | "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", 96 | "requires": { 97 | "color-name": "1.1.3" 98 | } 99 | }, 100 | "color-name": { 101 | "version": "1.1.3", 102 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 103 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 104 | }, 105 | "commander": { 106 | "version": "2.11.0", 107 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", 108 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" 109 | }, 110 | "concat-map": { 111 | "version": "0.0.1", 112 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 113 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 114 | }, 115 | "debug": { 116 | "version": "3.1.0", 117 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 118 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 119 | "requires": { 120 | "ms": "2.0.0" 121 | } 122 | }, 123 | "diff": { 124 | "version": "3.5.0", 125 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 126 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" 127 | }, 128 | "escape-string-regexp": { 129 | "version": "1.0.5", 130 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 131 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 132 | }, 133 | "fp-ts": { 134 | "version": "1.4.1", 135 | "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.4.1.tgz", 136 | "integrity": "sha512-+quy9y6X7F4F5Zb9ooMapeAcIbgcKGcyQPMNMCgi7fhHxhUMEIrOH9yPvkWRc83rUtL+AKaa3NTZa49KL/d4OQ==" 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.2", 145 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 146 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 147 | "requires": { 148 | "fs.realpath": "1.0.0", 149 | "inflight": "1.0.6", 150 | "inherits": "2.0.3", 151 | "minimatch": "3.0.4", 152 | "once": "1.4.0", 153 | "path-is-absolute": "1.0.1" 154 | } 155 | }, 156 | "growl": { 157 | "version": "1.10.3", 158 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", 159 | "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==" 160 | }, 161 | "has-flag": { 162 | "version": "2.0.0", 163 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 164 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" 165 | }, 166 | "he": { 167 | "version": "1.1.1", 168 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 169 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" 170 | }, 171 | "inflight": { 172 | "version": "1.0.6", 173 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 174 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 175 | "requires": { 176 | "once": "1.4.0", 177 | "wrappy": "1.0.2" 178 | } 179 | }, 180 | "inherits": { 181 | "version": "2.0.3", 182 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 183 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 184 | }, 185 | "isarray": { 186 | "version": "0.0.1", 187 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 188 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 189 | }, 190 | "just-extend": { 191 | "version": "1.1.27", 192 | "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", 193 | "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==" 194 | }, 195 | "lodash.get": { 196 | "version": "4.4.2", 197 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 198 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" 199 | }, 200 | "lolex": { 201 | "version": "2.3.2", 202 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", 203 | "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==" 204 | }, 205 | "make-error": { 206 | "version": "1.3.4", 207 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", 208 | "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==" 209 | }, 210 | "minimatch": { 211 | "version": "3.0.4", 212 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 213 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 214 | "requires": { 215 | "brace-expansion": "1.1.11" 216 | } 217 | }, 218 | "minimist": { 219 | "version": "0.0.8", 220 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 221 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 222 | }, 223 | "mkdirp": { 224 | "version": "0.5.1", 225 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 226 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 227 | "requires": { 228 | "minimist": "0.0.8" 229 | } 230 | }, 231 | "mocha": { 232 | "version": "5.1.1", 233 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz", 234 | "integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==", 235 | "requires": { 236 | "browser-stdout": "1.3.1", 237 | "commander": "2.11.0", 238 | "debug": "3.1.0", 239 | "diff": "3.5.0", 240 | "escape-string-regexp": "1.0.5", 241 | "glob": "7.1.2", 242 | "growl": "1.10.3", 243 | "he": "1.1.1", 244 | "minimatch": "3.0.4", 245 | "mkdirp": "0.5.1", 246 | "supports-color": "4.4.0" 247 | } 248 | }, 249 | "ms": { 250 | "version": "2.0.0", 251 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 252 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 253 | }, 254 | "nise": { 255 | "version": "1.3.3", 256 | "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", 257 | "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", 258 | "requires": { 259 | "@sinonjs/formatio": "2.0.0", 260 | "just-extend": "1.1.27", 261 | "lolex": "2.3.2", 262 | "path-to-regexp": "1.7.0", 263 | "text-encoding": "0.6.4" 264 | } 265 | }, 266 | "once": { 267 | "version": "1.4.0", 268 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 269 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 270 | "requires": { 271 | "wrappy": "1.0.2" 272 | } 273 | }, 274 | "path-is-absolute": { 275 | "version": "1.0.1", 276 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 277 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 278 | }, 279 | "path-to-regexp": { 280 | "version": "1.7.0", 281 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", 282 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 283 | "requires": { 284 | "isarray": "0.0.1" 285 | } 286 | }, 287 | "samsam": { 288 | "version": "1.3.0", 289 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", 290 | "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==" 291 | }, 292 | "sinon": { 293 | "version": "5.0.6", 294 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-5.0.6.tgz", 295 | "integrity": "sha512-xn1jBaHFJMAUaYSa7Fr9gHGopcjSo128kQKDaLIUCM3s6x687nqdtjkxhu4IonbBS1qgXf/u17i7sEvwFgQyhg==", 296 | "requires": { 297 | "@sinonjs/formatio": "2.0.0", 298 | "diff": "3.5.0", 299 | "lodash.get": "4.4.2", 300 | "lolex": "2.3.2", 301 | "nise": "1.3.3", 302 | "supports-color": "5.4.0", 303 | "type-detect": "4.0.8" 304 | }, 305 | "dependencies": { 306 | "has-flag": { 307 | "version": "3.0.0", 308 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 309 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 310 | }, 311 | "supports-color": { 312 | "version": "5.4.0", 313 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 314 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 315 | "requires": { 316 | "has-flag": "3.0.0" 317 | } 318 | } 319 | } 320 | }, 321 | "source-map": { 322 | "version": "0.6.1", 323 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 324 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 325 | }, 326 | "source-map-support": { 327 | "version": "0.5.5", 328 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.5.tgz", 329 | "integrity": "sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA==", 330 | "requires": { 331 | "buffer-from": "1.0.0", 332 | "source-map": "0.6.1" 333 | } 334 | }, 335 | "supports-color": { 336 | "version": "4.4.0", 337 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", 338 | "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", 339 | "requires": { 340 | "has-flag": "2.0.0" 341 | } 342 | }, 343 | "text-encoding": { 344 | "version": "0.6.4", 345 | "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", 346 | "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=" 347 | }, 348 | "ts-node": { 349 | "version": "6.0.3", 350 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.3.tgz", 351 | "integrity": "sha512-ARaOMNFEPKg2ZuC1qJddFvHxHNFVckR0g9xLxMIoMqSSIkDc8iS4/LoV53EdDWWNq2FGwqcEf0bVVGJIWpsznw==", 352 | "requires": { 353 | "arrify": "1.0.1", 354 | "chalk": "2.4.1", 355 | "diff": "3.5.0", 356 | "make-error": "1.3.4", 357 | "minimist": "1.2.0", 358 | "mkdirp": "0.5.1", 359 | "source-map-support": "0.5.5", 360 | "yn": "2.0.0" 361 | }, 362 | "dependencies": { 363 | "minimist": { 364 | "version": "1.2.0", 365 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 366 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 367 | } 368 | } 369 | }, 370 | "type-detect": { 371 | "version": "4.0.8", 372 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 373 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" 374 | }, 375 | "typescript": { 376 | "version": "2.8.3", 377 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz", 378 | "integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==" 379 | }, 380 | "wrappy": { 381 | "version": "1.0.2", 382 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 383 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 384 | }, 385 | "yn": { 386 | "version": "2.0.0", 387 | "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", 388 | "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=" 389 | } 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exploring-fp-ts-series", 3 | "version": "1.0.0", 4 | "description": "Supporting code for the Exploring fp-ts series", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --require ts-node/register --recursive './src/**/*.ts'", 8 | "build": "tsc" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/davetayls/exploring-fp-ts-series.git" 13 | }, 14 | "author": "Dave Taylor ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/davetayls/exploring-fp-ts-series/issues" 18 | }, 19 | "homepage": "https://github.com/davetayls/exploring-fp-ts-series#readme", 20 | "dependencies": { 21 | "@types/mocha": "^5.2.0", 22 | "@types/node": "^10.0.4", 23 | "@types/sinon": "^4.3.1", 24 | "fp-ts": "^1.4.1", 25 | "mocha": "^5.1.1", 26 | "sinon": "^5.0.6", 27 | "ts-node": "^6.0.3", 28 | "typescript": "^2.8.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/01-nullable-values/nullable-values.test.ts: -------------------------------------------------------------------------------- 1 | import { equal, deepEqual } from 'assert' 2 | import { fromNullable, Option, option, some } from 'fp-ts/lib/Option' 3 | import { traverse } from 'fp-ts/lib/Array' 4 | 5 | describe('nullable values', function () { 6 | 7 | /** 8 | * Should run the first function passed to 9 | * fold when the value is null or undefined. 10 | * 11 | * This is the simplest way of using option 12 | */ 13 | it( 14 | 'Should call the first argument of fold', 15 | function () { 16 | const myVal = null 17 | fromNullable(myVal) 18 | .fold( 19 | // the first argument (or left) is 20 | // run when the value is null or undefined 21 | () => { 22 | equal(myVal, null) 23 | }, 24 | // The second argument (or right) is 25 | // run when the value exists 26 | () => { 27 | throw new Error('This should not run') 28 | } 29 | ) 30 | } 31 | ) 32 | 33 | it( 34 | 'Should pull a list of names', 35 | function () { 36 | const names = [ 37 | 'Bob Smith', 38 | 'Andy Hedge', 39 | null, 40 | 'Helen Newbury', 41 | undefined 42 | ] 43 | 44 | const getFirstName = (name: string | null | undefined) => 45 | fromNullable(name) 46 | .map((name) => name.split(' ')[0]) 47 | 48 | const defaultFirstName = (name: string | null | undefined) => 49 | getFirstName(name).alt(some('No name')) 50 | 51 | const result = traverse(option)(names, defaultFirstName) 52 | .getOrElse([]) 53 | 54 | deepEqual(result, [ 55 | 'Bob', 56 | 'Andy', 57 | 'No name', 58 | 'Helen', 59 | 'No name' 60 | ]) 61 | } 62 | ) 63 | 64 | }) 65 | -------------------------------------------------------------------------------- /src/02-handling-errors/handling-errors.test.ts: -------------------------------------------------------------------------------- 1 | import { equal, deepEqual } from 'assert' 2 | import { Either, either, fromNullable, left, right, tryCatch } from 'fp-ts/lib/Either' 3 | import { traverse } from 'fp-ts/lib/Array' 4 | 5 | interface IPerson { 6 | recurringPayment?: number 7 | } 8 | 9 | interface IError { 10 | name: string 11 | message: string 12 | } 13 | 14 | describe('handling errors', function () { 15 | 16 | const getRecurringPayment = (person: IPerson): Either => 17 | fromNullable(new Error('No Recurring Payment'))(person.recurringPayment) 18 | 19 | const niceNameCheck = (name: string) => { 20 | if (/dude/i.test(name)) { 21 | return 'Nice name' 22 | } else { 23 | throw new Error('Bad name') 24 | } 25 | } 26 | 27 | /** 28 | * A very simple implementation of resolveCommonError 29 | */ 30 | const resolveCommonError = (err: any): IError => { 31 | if (err instanceof Error) { 32 | return err 33 | } else if (typeof err === 'string') { 34 | return new Error(err) 35 | } else { 36 | return new Error('Unknown error') 37 | } 38 | } 39 | 40 | it('should pass the error to the left fold function', function () { 41 | const myVal = {} 42 | getRecurringPayment(myVal) 43 | .fold( 44 | // the first argument (or left) is 45 | // run when the value is null or undefined 46 | (err) => { 47 | equal(err.message, 'No Recurring Payment') 48 | }, 49 | // The second argument (or right) is 50 | // run when the value exists 51 | () => { 52 | throw new Error('This should not run') 53 | } 54 | ) 55 | }) 56 | 57 | it('should skip to end if a left is returned from chain', function () { 58 | getRecurringPayment({ recurringPayment: 5 }) 59 | .chain(() => left(new Error('Must be more than 10'))) 60 | .fold( 61 | // the first argument (or left) is 62 | // run when the value is null or undefined 63 | (err) => { 64 | equal(err.message, 'Must be more than 10') 65 | }, 66 | // The second argument (or right) is 67 | // run when the code succeeds 68 | () => { 69 | throw new Error('This should not run') 70 | } 71 | ) 72 | }) 73 | 74 | it('should get the result at the end if the steps all work', function () { 75 | getRecurringPayment({ recurringPayment: 5 }) 76 | .map((recurringPayment) => recurringPayment * 12) 77 | .map((amount) => amount > 1000 ? 'Too expensive' : 'Affordable') 78 | .fold( 79 | (err) => { 80 | throw err 81 | }, 82 | // The second argument (or right) is run when the code succeeds 83 | (affordability) => { 84 | equal(affordability, 'Affordable') 85 | } 86 | ) 87 | }) 88 | 89 | it('should catch an internally thrown error', function () { 90 | getRecurringPayment({ recurringPayment: 5 }) 91 | .map((recurringPayment) => recurringPayment * 12) 92 | .map((amount) => amount > 1000 ? 'Too expensive' : 'Affordable') 93 | .chain(() => tryCatch(() => { 94 | throw 'Bad JS Error' 95 | }, resolveCommonError)) 96 | .fold( 97 | (err) => { 98 | equal(err.name, 'Error') 99 | equal(err.message, 'Bad JS Error') 100 | }, 101 | // The second argument (or right) is run when the code succeeds 102 | () => { 103 | throw new Error('This should not run') 104 | } 105 | ) 106 | }) 107 | 108 | it('Should fail with an error', function () { 109 | const names = [ 110 | 'Bob Smith', 111 | 'Andy Hedge', 112 | 'Helen Newbury', 113 | ] 114 | 115 | const niceNameDude = (name: string) => 116 | tryCatch(() => niceNameCheck(name), resolveCommonError) 117 | 118 | const result = traverse(either)(names, niceNameDude) 119 | .getOrElse(['Not all names were good']) 120 | 121 | deepEqual(result, ['Not all names were good']) 122 | }) 123 | 124 | it('Should provide alternative for bad names', function () { 125 | const names = [ 126 | 'Dude Smith', 127 | 'Andy Hedge' 128 | ] 129 | 130 | const niceNameDude = (name: string) => 131 | tryCatch(() => niceNameCheck(name), resolveCommonError) 132 | .alt(right('Be a dude')) 133 | 134 | const result = traverse(either)(names, niceNameDude) 135 | .getOrElse([]) 136 | 137 | deepEqual(result, ['Nice name', 'Be a dude']) 138 | }) 139 | 140 | }) 141 | -------------------------------------------------------------------------------- /src/03-async/async.test.ts: -------------------------------------------------------------------------------- 1 | import { equal, deepEqual } from 'assert' 2 | import { Either, either, fromNullable, left, right, tryCatch } from 'fp-ts/lib/Either' 3 | import { taskEither } from 'fp-ts/lib/TaskEither' 4 | import { traverse } from 'fp-ts/lib/Array' 5 | 6 | interface IPerson { 7 | recurringPayment?: number 8 | } 9 | 10 | describe('async', function () { 11 | 12 | const getRecurringPayment = (person: IPerson): Either => 13 | fromNullable(new Error('No Recurring Payment'))(person.recurringPayment) 14 | 15 | const niceNameCheck = (name: string) => { 16 | if (/dude/i.test(name)) { 17 | return 'Nice name' 18 | } else { 19 | throw new Error('Bad name') 20 | } 21 | } 22 | 23 | 24 | it('should pass the error to the left fold function', function () { 25 | return taskEither.of('hello') 26 | .map(() => { 27 | throw new Error('aaaaaa') 28 | }) 29 | .fold( 30 | // the first argument (or left) is 31 | // run when the value is null or undefined 32 | (err) => { 33 | equal(err, 'No Recurring Payment') 34 | }, 35 | // The second argument (or right) is 36 | // run when the value exists 37 | () => { 38 | throw new Error('This should not run') 39 | } 40 | ) 41 | .run() 42 | }) 43 | 44 | }) 45 | -------------------------------------------------------------------------------- /src/lib/env.ts: -------------------------------------------------------------------------------- 1 | export const readEnv = () => process.env 2 | -------------------------------------------------------------------------------- /src/lib/errors.ts: -------------------------------------------------------------------------------- 1 | 2 | interface IError { 3 | name: string 4 | message: string 5 | } 6 | 7 | /** 8 | * A very simple implementation of resolveCommonError 9 | */ 10 | export const resolveCommonError = (err: any): IError => { 11 | if (err instanceof Error) { 12 | return err 13 | } else if (typeof err === 'string') { 14 | return new Error(err) 15 | } else { 16 | return new Error('Unknown error') 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/middleware/chain-of-responsibility.ts: -------------------------------------------------------------------------------- 1 | import { buildMiddleware, TMiddleware } from './middleware' 2 | 3 | const env = { 4 | getBalance: () => 500 5 | } 6 | 7 | interface IChainOfResponsibility { 8 | name: string 9 | ones: number 10 | tens?: number 11 | twenties?: number 12 | hundreds?: number 13 | hasEnoughMoney?: string 14 | } 15 | 16 | const hasEnoughMoney: TMiddleware = 17 | (env, next) => atm => { 18 | const result = { 19 | ...atm, 20 | hasEnoughMoney: atm.ones < env.getBalance() ? 'Yes!' : 'No' 21 | } 22 | return next(result) 23 | } 24 | 25 | const hundreds: TMiddleware = 26 | (env, next) => atm => { 27 | const ones = atm.ones % 100 28 | const result = { 29 | ...atm, 30 | hundreds: (atm.ones - ones) / 100, 31 | ones 32 | } 33 | return next(result) 34 | } 35 | 36 | const twenties: TMiddleware = 37 | (env, next) => atm => { 38 | const ones = atm.ones % 20 39 | const result = { 40 | ...atm, 41 | twenties: (atm.ones - ones) / 20, 42 | ones 43 | } 44 | return next(result) 45 | } 46 | 47 | const tens: TMiddleware = 48 | (env, next) => atm => { 49 | const ones = atm.ones % 10 50 | const result = { 51 | ...atm, 52 | tens: (atm.ones - ones) / 10, 53 | ones 54 | } 55 | return next(result) 56 | } 57 | 58 | export const handleRequestPipeline = 59 | buildMiddleware(hasEnoughMoney, hundreds, twenties, tens) 60 | export const chainOfResponsibility = handleRequestPipeline(env) 61 | -------------------------------------------------------------------------------- /src/middleware/middleware.test.ts: -------------------------------------------------------------------------------- 1 | import { equal, deepEqual } from 'assert' 2 | import { chainOfResponsibility } from './chain-of-responsibility' 3 | import { getSweetsSentence } from './sweets' 4 | import { fetchPerson } from './promises' 5 | 6 | describe('middleware', function () { 7 | 8 | describe('chain of responsibility', function () { 9 | 10 | it('Bob asks for £1335', function () { 11 | deepEqual( 12 | chainOfResponsibility({ name: 'Bob', ones: 1335 }), 13 | { 14 | name: 'Bob', 15 | hasEnoughMoney: 'No', 16 | ones: 5, 17 | tens: 1, 18 | twenties: 1, 19 | hundreds: 13 20 | } 21 | ) 22 | }) 23 | 24 | it('Mary asks for £13.50', function () { 25 | deepEqual( 26 | chainOfResponsibility({ name: 'Mary', ones: 13.50 }), 27 | { 28 | name: 'Mary', 29 | hasEnoughMoney: 'Yes!', 30 | ones: 3.5, 31 | tens: 1, 32 | twenties: 0, 33 | hundreds: 0 34 | } 35 | ) 36 | }) 37 | 38 | }) 39 | 40 | describe('sweets', function () { 41 | 42 | it('should have eaten a lot of sweets', function () { 43 | equal( 44 | getSweetsSentence('Barney'), 45 | 'Barney ate 20 sweets and enjoyed it.' 46 | ) 47 | }) 48 | 49 | }) 50 | 51 | describe('promises', function () { 52 | 53 | it('should successfully fetch helen', function () { 54 | return fetchPerson({ data: { id: 'helen' } }) 55 | .then((res) => { 56 | equal(res.data!.name, 'Helen') 57 | equal(res.data!.age, 20) 58 | }) 59 | }) 60 | 61 | it('should fail with bob', function () { 62 | return fetchPerson({ data: { id: 'bob' } }) 63 | .then(() => { 64 | throw new Error('should not run') 65 | }) 66 | .catch((err) => { 67 | equal(err.message, 'Must be an adult') 68 | }) 69 | }) 70 | 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /src/middleware/middleware.ts: -------------------------------------------------------------------------------- 1 | import { Either, fromPredicate, left, right } from 'fp-ts/lib/Either' 2 | import { fromEither, TaskEither, right as taskEitherRight } from 'fp-ts/lib/TaskEither' 3 | import { Task } from 'fp-ts/lib/Task' 4 | 5 | export type TMiddleware = 6 | (env: Env, next: (context: Context) => Context) => 7 | (context: Context) => Context 8 | 9 | 10 | export const buildMiddleware = 11 | (...middlewares: Array>) => 12 | (env: Env) => 13 | (req: Context): Context => { 14 | const runFinal = (context: any) => context 15 | const chain = middlewares 16 | .reduceRight( 17 | (next: any, middleware) => middleware(env, next), 18 | runFinal 19 | ) 20 | return chain(req) 21 | } 22 | 23 | 24 | // Either pipeline 25 | 26 | // const a: TMiddleware> = (env, next) => ctx => { 27 | // console.log('a', ctx) 28 | // return next(ctx.map(x => ({ ...x, res: 1 }))) 29 | // } 30 | // const b: TMiddleware> = (env, next) => ctx => { 31 | // console.log('b', ctx) 32 | // return next(ctx.chain(() => 33 | // left({ 34 | // error: new Error('boom') 35 | // }) 36 | // )) 37 | // } 38 | // 39 | // const c: TMiddleware> = (env, next) => ctx => { 40 | // console.log('c', ctx) 41 | // return next(ctx.chain(() => 42 | // left({ 43 | // data: 20 44 | // }) 45 | // )) 46 | // } 47 | // 48 | // const eitherHandler = (ctx: Either): IResponse => { 49 | // console.log('either handler', ctx) 50 | // return ctx.fold((res) => res, (req) => ({ error: new Error('No request was made') })) 51 | // } 52 | // 53 | // console.log('Either', buildMiddleware(a, b, c)({}, eitherHandler)(right({ data: 0 }))) 54 | // 55 | // // TaskEither 56 | // 57 | // const teA: TMiddleware> = (env, next) => ctx => { 58 | // console.log('a', ctx) 59 | // return next(ctx.map(x => ({ ...x, res: 1 }))) 60 | // } 61 | // const teFail: TMiddleware> = (env, next) => ctx => { 62 | // console.log('b', ctx) 63 | // return next(ctx.chain(() => 64 | // fromEither(left({ 65 | // error: new Error('boom') 66 | // })) 67 | // )) 68 | // } 69 | // 70 | // const teSucceed: TMiddleware> = (env, next) => ctx => { 71 | // console.log('c', ctx) 72 | // return next(ctx.chain(() => 73 | // fromEither(left({ 74 | // data: 20 75 | // })) 76 | // )) 77 | // } 78 | // 79 | // const taskEitherHandler: THandler, TaskEither> = (ctx) => { 80 | // console.log('task either handler', ctx) 81 | // const task: Task = ctx 82 | // .fold( 83 | // (res) => res, 84 | // (req) => ({ error: new Error('No request was made') }) 85 | // ) 86 | // return taskEitherRight(task) 87 | // .chain((res) => { 88 | // if (res.error) { 89 | // return fromEither(left(res)) 90 | // } else { 91 | // return fromEither(right(res)) 92 | // } 93 | // }) 94 | // } 95 | // 96 | // const taskEitherPipeline = 97 | // buildMiddleware(teA, teSucceed, teFail)({}, taskEitherHandler) 98 | // 99 | // taskEitherPipeline(fromEither(right({ data: 0 }))) 100 | // .fold( 101 | // (err) => { 102 | // console.log('TaskEither error', err) 103 | // }, 104 | // (res: any) => { 105 | // console.log('TaskEither', res) 106 | // } 107 | // ) 108 | // .run() 109 | // 110 | -------------------------------------------------------------------------------- /src/middleware/promises.ts: -------------------------------------------------------------------------------- 1 | // what about with promises 2 | 3 | import { buildMiddleware, TMiddleware } from './middleware' 4 | 5 | interface IData { 6 | id: string 7 | name: string 8 | age: number 9 | } 10 | 11 | interface IRequest { 12 | data: { id: string } 13 | } 14 | 15 | interface IResponse { 16 | data?: IData 17 | error?: Error 18 | } 19 | 20 | interface IContext { 21 | req: IRequest 22 | res: IResponse 23 | } 24 | 25 | const data: { [id: string]: IData } = { 26 | helen: { id: 'helen', name: 'Helen', age: 20 }, 27 | bob: { id: 'bob', name: 'Bob', age: 16 } 28 | } 29 | 30 | const fetchFromServer: TMiddleware<{}, Promise> = 31 | (env, next) => promise => 32 | next( 33 | promise.then((ctx) => { 34 | // Just mock an api call 35 | return new Promise((resolve) => { 36 | setTimeout(() => { 37 | ctx.res = { 38 | data: data[ctx.req.data.id] 39 | } 40 | resolve(ctx) 41 | }, 500) 42 | }) 43 | }) 44 | ) 45 | 46 | const mustBeAdult: TMiddleware> = 47 | (env, next) => promise => 48 | next( 49 | promise.then((ctx) => { 50 | if (ctx.res && ctx.res.data && ctx.res.data.age < 18) { 51 | return { 52 | ...ctx, 53 | res: { 54 | error: new Error('Must be an adult') 55 | } 56 | } 57 | } else { 58 | return ctx 59 | } 60 | }) 61 | ) 62 | 63 | const buildPersonDataFetcher = buildMiddleware( 64 | fetchFromServer, 65 | mustBeAdult 66 | ) 67 | 68 | export const fetchPerson = (req: IRequest) => 69 | buildPersonDataFetcher({})(Promise.resolve({ req, res: {} })) 70 | .then((ctx) => { 71 | if (ctx.res.error) { 72 | throw ctx.res.error 73 | } else { 74 | return ctx.res 75 | } 76 | }) 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/middleware/sweets.ts: -------------------------------------------------------------------------------- 1 | 2 | // First middleware 3 | import { buildMiddleware, TMiddleware } from './middleware' 4 | 5 | interface ISweets { 6 | isTakingPart: () => boolean, 7 | didEnjoy: () => boolean, 8 | numberOfSweets: () => number 9 | } 10 | 11 | const sweets: TMiddleware = (env, next) => context => { 12 | context += ` ate ${env.numberOfSweets()} sweets` 13 | return next(context) 14 | } 15 | 16 | // Second middleware 17 | const enjoyed: TMiddleware = (env, next) => context => { 18 | if (env.didEnjoy()) { 19 | context += ' and enjoyed it.' 20 | } else { 21 | context += ' and stuck tongue out.' 22 | } 23 | return next(context) 24 | } 25 | 26 | // We can stop the middleware chain 27 | // and return early if needed 28 | const early: TMiddleware = (env, next) => context => { 29 | if (env.isTakingPart()) { 30 | return next(context) 31 | } else { 32 | return context + ' did not want to take part' 33 | } 34 | } 35 | 36 | const buildSweetsSentenceWith = buildMiddleware( 37 | early, 38 | sweets, 39 | enjoyed 40 | ) 41 | 42 | // Apply the environment to the pipeline 43 | export const getSweetsSentence = buildSweetsSentenceWith({ 44 | isTakingPart: () => true, 45 | didEnjoy: () => true, 46 | numberOfSweets: () => 20 47 | }) 48 | -------------------------------------------------------------------------------- /src/pointfree/composing.test.ts: -------------------------------------------------------------------------------- 1 | import { equal } from 'assert' 2 | import { stub, SinonStub } from 'sinon' 3 | import { 4 | allBigAndBold, beReallyWelcoming, calcSomething, eitherHello, isBigAndBold, join, renderTitle, 5 | sequenceTaskEither, 6 | soBold 7 | } from './composing' 8 | import { identity } from 'fp-ts/lib/function' 9 | 10 | describe('composing', function () { 11 | 12 | describe('simple strings', function () { 13 | 14 | it('should be really welcoming', function () { 15 | equal(beReallyWelcoming('well'), 'WELL HELLO') 16 | }) 17 | 18 | }) 19 | 20 | describe('and', function () { 21 | it('should find big and bold', function () { 22 | equal(isBigAndBold('big and bold'), true) 23 | }) 24 | it('should force bold', function () { 25 | equal(soBold('big'), true) 26 | }) 27 | }) 28 | 29 | describe('either', function () { 30 | it('should use the provided string', function () { 31 | equal( 32 | eitherHello('world').fold(identity, identity), 33 | 'hello world') 34 | }) 35 | it('should use the default string', function () { 36 | equal( 37 | eitherHello(null).fold(identity, identity), 38 | 'yo!!') 39 | }) 40 | }) 41 | 42 | describe('task', function () { 43 | it('should ', function () { 44 | return renderTitle 45 | .map((s) => { 46 | equal(s, 'Hello Dave, you are 89') 47 | }) 48 | .run() 49 | }) 50 | }) 51 | 52 | describe('taskEither', function () { 53 | it('should ', function () { 54 | calcSomething 55 | .fold( 56 | (e) => console.log('left', e), 57 | (d) => console.log('right', d) 58 | ) 59 | .run() 60 | }) 61 | }) 62 | 63 | describe('traversing with either', function () { 64 | it('should be big and bold', function () { 65 | const result = allBigAndBold(['big and bold', 'bold and big']) 66 | equal(result.isRight(), true) 67 | equal( 68 | result.map(join(',')).getOrElse(''), 69 | 'big and bold,bold and big' 70 | ) 71 | }) 72 | it('should be a left', function () { 73 | const result = allBigAndBold([ 74 | 'not b or b', 75 | 'bold and big' 76 | ]) 77 | equal(result.isLeft(), true) 78 | result.fold( 79 | (s) => { 80 | equal(s, 'No!') 81 | }, 82 | () => { 83 | throw new Error('should not run') 84 | } 85 | ) 86 | }) 87 | }) 88 | 89 | describe('Sequence TaskEither', function () { 90 | it('should ', function () { 91 | return sequenceTaskEither() 92 | .then((e) => { 93 | e.fold( 94 | console.log, 95 | console.log 96 | ) 97 | }) 98 | .catch((err) => { 99 | console.log('err', err) 100 | }) 101 | }) 102 | }) 103 | 104 | }) 105 | -------------------------------------------------------------------------------- /src/pointfree/composing.ts: -------------------------------------------------------------------------------- 1 | import { IO } from 'fp-ts/lib/IO' 2 | import { and, compose, curry, flip } from 'fp-ts/lib/function' 3 | import { Either, either, fromNullable, left, right } from 'fp-ts/lib/Either' 4 | import { liftA2 } from 'fp-ts/lib/Apply' 5 | import { readEnv } from '../lib/env' 6 | import { Task, task } from 'fp-ts/lib/Task' 7 | import { array } from 'fp-ts/lib/Array' 8 | import { sequence, traverse } from 'fp-ts/lib/Traversable' 9 | import { fromEither, taskEither, tryCatch } from 'fp-ts/lib/TaskEither' 10 | import { flatten } from 'fp-ts/lib/Chain' 11 | import { StrMap, strmap } from 'fp-ts/lib/StrMap' 12 | import { validation } from 'fp-ts/lib/Validation' 13 | 14 | const toUpper = (s: string) => s.toUpperCase() 15 | const str = (s1: string) => (s2: string) => s1 + s2 16 | export const join = (s: string) => (arr: any[]) => arr.join(s) 17 | 18 | export const beReallyWelcoming = compose( 19 | toUpper, 20 | flip(str)(' hello') 21 | ) 22 | 23 | const isBig = (s: string) => /big/.test(s) 24 | const isBold = (s: string) => /bold/.test(s) 25 | export const isBigAndBold = and(isBig, isBold) 26 | 27 | export const soBold = compose( 28 | isBigAndBold, 29 | str('bold') 30 | ) 31 | 32 | /** 33 | * Composing with either 34 | */ 35 | const eitherMap = 36 | (f: (a: R) => B) => 37 | (e: Either): Either => e.map(f) 38 | 39 | const eitherNullable = 40 | (leftValue: L) => 41 | (r: R | null | undefined): Either => fromNullable(leftValue)(r) 42 | 43 | export const eitherHello = compose( 44 | eitherMap(str('hello ')), 45 | eitherNullable('yo!!') 46 | ) 47 | 48 | /** 49 | * Composing with Task 50 | */ 51 | const renderPage = curry((name: string, age: string) => `Hello ${name}, you are ${age}`) 52 | const liftedRender = liftA2(task)(renderPage) 53 | const taskName = new Task(() => 54 | new Promise((resolve) => { 55 | setTimeout(() => resolve('Dave'), 1000) 56 | }) 57 | ) 58 | const taskAge = task.of('89') 59 | export const renderTitle = liftedRender(taskName)(taskAge) 60 | 61 | /** 62 | * TaskEither 63 | */ 64 | const doSomething = (a: number) => (b: number) => fromEither(left(new Error(`boo ${a} ${b}`))) //taskEither.of(a + b) 65 | const liftedSomething = liftA2(taskEither)(doSomething) 66 | export const calcSomething = flatten(taskEither)( 67 | liftedSomething(taskEither.of(1))(taskEither.of(2)) 68 | ) 69 | 70 | /** 71 | * Ap 72 | */ 73 | const age = either.of(20) 74 | const many = either.of(4) 75 | const timesAge = (age: number) => (times: number) => age * times 76 | age.ap(many.map(timesAge)) 77 | 78 | /** 79 | * Traversing with Either 80 | */ 81 | const isBigAndBoldEither: (a: string) => Either = 82 | (a: string) => isBigAndBold(a) ? right(a) : left('No!') 83 | 84 | const traverseArrayEither = 85 | (predicate: (val: T) => Either) => 86 | (arr: T[]): Either => 87 | traverse(either, array)(arr, predicate) 88 | 89 | export const allBigAndBold = traverseArrayEither(isBigAndBoldEither) 90 | 91 | /** 92 | * sequence lefts 93 | */ 94 | // const sequenceValidation = sequence(validation, array) 95 | // 96 | // function validate(input: Array, A>>): Either, Array> { 97 | // const toValidation = fromEither(getArrayMonoid()) 98 | // return sequenceValidation(input.map(toValidation)).toEither() 99 | // } 100 | // 101 | // console.log(validate([right(1), right(2)])) // right([1, 2]) 102 | // console.log(validate([right(1), left(['a'])])) // left(["a"]) 103 | // console.log(validate([left(['a']), left(['b'])])) // left(["a", "b"]) 104 | 105 | /** 106 | * sequence TaskEither 107 | */ 108 | export const sequenceTaskEither = () => { 109 | const arr = [ 110 | taskEither.of(1), 111 | taskEither.of(2), 112 | tryCatch( 113 | () => new Promise((r, rej) => { 114 | console.log('te 1', new Date()) 115 | return setTimeout(() => rej(new Error('1')), 1100) 116 | }), 117 | (err) => err 118 | ), 119 | tryCatch( 120 | () => new Promise((r, rej) => { 121 | console.log('te 2', new Date()) 122 | return setTimeout(() => rej(new Error('2')), 1000) 123 | }), 124 | (err) => err 125 | ) 126 | ] 127 | return sequence(taskEither, array)(arr) 128 | // .map(([a, b, c]) => { 129 | // return a + b + c 130 | // }) 131 | .run() 132 | 133 | // const m = new StrMap({ 134 | // a: taskEither.of(1), 135 | // b: taskEither.of(2), 136 | // c: tryCatch( 137 | // () => new Promise((r, rej) => 138 | // setTimeout(() => rej(new Error('1')), 1100)), 139 | // (err) => err 140 | // ), 141 | // d: tryCatch( 142 | // () => new Promise((r, rej) => 143 | // setTimeout(() => rej(new Error('2')), 1000)), 144 | // (err) => err 145 | // ) 146 | // }) 147 | // return sequence(taskEither, strmap)(m) 148 | // // .map((a) => { 149 | // // console.log('map', a) 150 | // // return a 151 | // // }) 152 | // .run() 153 | } 154 | -------------------------------------------------------------------------------- /src/unknown_changes/io.test.ts: -------------------------------------------------------------------------------- 1 | import { SinonStub, stub } from 'sinon' 2 | import * as env from '../lib/env' 3 | import { equal } from 'assert' 4 | import { iLoveEditor } from './io' 5 | 6 | describe('io', function () { 7 | let envStub: SinonStub 8 | 9 | beforeEach(function () { 10 | envStub = stub(env, 'readEnv') 11 | }) 12 | 13 | afterEach(function () { 14 | envStub.restore() 15 | }) 16 | 17 | it('does not know what it likes', function () { 18 | envStub.returns({}) 19 | equal(iLoveEditor.run(), 'I love webstorm') 20 | }) 21 | 22 | it('should like webstorm', function () { 23 | envStub.returns({ EDITOR: 'webstorm' }) 24 | equal(iLoveEditor.run(), 'I love webstorm') 25 | }) 26 | 27 | }) 28 | -------------------------------------------------------------------------------- /src/unknown_changes/io.ts: -------------------------------------------------------------------------------- 1 | import { IO } from 'fp-ts/lib/IO' 2 | import { readEnv } from '../lib/env' 3 | import { fromNullable, option } from 'fp-ts/lib/Option' 4 | 5 | /** 6 | * IO 7 | * 8 | * Use when 9 | * - You are not in control of an underlying value 10 | * 11 | * Example wrapping process.env 12 | */ 13 | 14 | const ioEnv = new IO(() => readEnv()) 15 | export const iLoveEditor = 16 | ioEnv 17 | .map((env) => 18 | fromNullable(env.EDITOR) 19 | .map((s) => `I love ${s}`) 20 | ) 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "lib": ["es6", "dom"], 7 | "sourceMap": true, 8 | "strict": true 9 | } 10 | } 11 | --------------------------------------------------------------------------------