├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples ├── game.ts └── unsafeRun.ts ├── package-lock.json ├── package.json ├── src ├── Distributes.ts ├── bikleisli-io.ts ├── bikleisli.ts ├── error.ts ├── index.ts ├── kleisli-io.test.ts ├── kleisli-io.ts ├── kleisli.test.ts ├── kleisli.ts └── unsafe.ts ├── tsconfig.json ├── tsconfig.production.json └── tslint.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Small code snippet to reproduce the behavior. You may want to use some online code sandboxes like https://codesandbox.io. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .vscode/ 64 | trash/ 65 | lib/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | stages: 5 | - test 6 | - name: npm release 7 | if: tag IS present 8 | jobs: 9 | include: 10 | - stage: test 11 | script: npm test 12 | - stage: npm release 13 | if: tag IS present 14 | node_js: '10' 15 | script: npm run build 16 | deploy: 17 | provider: npm 18 | email: yuriy.bogomolov@gmail.com 19 | api_key: 20 | secure: DVsWTTDakkZYuqmgNqoGWl7ybrR4lq4z1ALuhqOBwgZbw++AQ51iQtNNGkspJT0avvCOkykqvRoBlhMPQAfWE7mQaaQXBGi4sHXwdr9Ud+ggrD8qjTGFqPz8Qx6C2bDq/JneTtwozuxqALykvSI1zqMrEx0rR+3fCns5AbgBYoR0Tbw6MnyS0LdbDy5z0wVAmtEGXZ9fQCDD9Kwq9klgdWxo9CPSA1dm+ZP07jKYsKNXFev2Z+ElJRpbXaTk45es3ykBe2IcbCUf7gjbPJQ44zrZ+H5qKbcjuJI23uJCK5it9SmwNHSPNu1aWM/jfB9QlrMLR9o5JNta7/uXktmEyGWRXBZ+L6m0b4v+XH7Uqk5vqhRMRMYtOnz7SdobMrSpSzvLVXCh1Qxke8oQ2F0woDIC8D8ScZtLDcwqY9GHbizgWOy3WuKyZN1kohmfjS/PdZnwkmgSaziHq7YhKkqNS/aj2utdmOo903LjKaQDigfIGNwxVjXqv2IJuJ9/Qqg9RbaGNFEvtkY0/mTPyJrBtSf9tGjMqADqE4Gpm8XLcftHbtMm5dLc/8wX2Eq8bB+fU5qZ2XlqUz1racw7lYE9oZPOA8xDWZOTXzTRSzBjlan7hoGuJaPm/ABGkxD1aKu4NyGuuDly+4UWHRbbM8jcy+qATCwLgKh0jKyh3dG9lYA= 21 | skip_cleanup: true 22 | on: 23 | tags: true 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # 1.0.0 – 2019-09-10 4 | ### Changed 5 | - \[Breaking\] Upgrade to `fp-ts@2`. 6 | 7 | # 0.3.1 – 2019-06-29 8 | ### Changed 9 | - All arrow functions are replaced by the regular old `function` due to fp-ts's `URIS` being expanded prematurely. 10 | 11 | # 0.3.0 – 2019-06-29 12 | ### Added 13 | - `BiKleisli` and `BiKleisliIO` instances to model an arrow of form `F -> G` and `F -> G`, respectively. 14 | 15 | # 0.2.1 - 2019-06-18 16 | ### Changed 17 | - Migrate to `fp-ts` v1.19.0 18 | 19 | # 0.2.0 – 2019-06-16 20 | ### Added 21 | - Tests (closes #2) 22 | ### Changed 23 | - `of` now expects input of type `B`, not `() => B`. 24 | 25 | # 0.1.1 - 2019-06-14 26 | ### Added 27 | - Convenience `getInstancesFor` method. 28 | - Changelog. 29 | 30 | # 0.1.0 - 2019-06-13 31 | ### Added 32 | - Two examples – one with simple unsafeRun usage, and the other with guessing game. 33 | - `chain` method. 34 | 35 | # 0.0.2 - 2019-06-13 36 | ### Chnaged 37 | - License was changed to Apache 2.0. 38 | 39 | # 0.0.1 – 2019-06-13 40 | ### Added 41 | - Initial release. 42 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at yuriy.bogomolov@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | First of all, welcome and thank you for your willingness to help! Please review the suggested steps 4 | below to ensure that your contribution is approved. 5 | 6 | When contributing to this repository, please first discuss the change you wish to make via issue, 7 | email, or any other method with the owners of this repository before making a change. 8 | 9 | Please note we have a [code of conduct](./CODE_OF_CONDUCT.md), so follow it in all your interactions with the project. 10 | 11 | ## Pull Request Process 12 | 13 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 14 | build. 15 | 2. Update the README.md with details of changes to the interfaces, new typeclass instances, etc. 16 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 17 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 18 | 4. Your Pull Request will be reviewed by the project authors, and merged when no questions are left. 19 | 20 | ## Setting up the project locally 21 | 22 | 1. Make sure to install the dependencies via `npm ci` command. It is crucial to use the same versions 23 | of packages which are fixed in `package-lock.json` file. 24 | 2. Make your changes and ensure that all tests or other checks complete without any warnings or errors. 25 | 26 | This file is a subject to change as the project evolves, so please be sure to review its changes. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kleisli arrows for bifunctor IO 2 | 3 | [![npm](https://img.shields.io/npm/v/kleisli-ts.svg)](https://www.npmjs.com/package/kleisli-ts) 4 | [![Build Status](https://travis-ci.org/YBogomolov/kleisli-ts.svg)](https://travis-ci.org/YBogomolov/kleisli-ts) 5 | 6 | Part of [fp-ts](https://github.com/gcanti/fp-ts) ecosystem. 7 | 8 | TypeScript port of `KleisliIO` – Kleisli arrows with bifunctor IO from great talk by [John A. De Goes](https://github.com/jdegoes) at LambdaConf'18 called ["Blazing Fast, Pure Effects without Monads"](https://www.youtube.com/watch?v=L8AEj6IRNEE). 9 | 10 | Please see [examples](./examples) for possible ways of programming with Kleisli arrows. 11 | 12 | ## Installation & usage 13 | 14 | 1. Install this module either via NPM or Yarn: 15 | ```sh 16 | npm i kleisli-ts 17 | # or 18 | yarn add kleisli-ts 19 | ``` 20 | 2. This module has a peer dependency – [fp-ts](https://github.com/gcanti/fp-ts), so you'll need to install it as well: 21 | ```sh 22 | npm i fp-ts@1 23 | yarn add fp-ts@1 24 | ``` 25 | 3. `kleisli-ts` provides curried functions as its main API, but you also have a convenience method `getInstancesFor`, which returns an API instance bound to the given monad: 26 | ```ts 27 | import { getInstancesFor } from 'kleisli-ts'; 28 | import { ioEither } from 'fp-ts/lib/IOEither'; 29 | 30 | const { liftK } = getInstancesFor(ioEither); 31 | 32 | const throwMe = liftK(() => { throw new Error('yay, it works'); }); 33 | ``` 34 | 35 | ## Simple example 36 | 37 | ```ts 38 | import { ioEither, URI as IOEitherURI } from 'fp-ts/lib/IOEither'; 39 | 40 | import { getInstancesFor, KleisliIO } from 'kleisli-ts/lib'; 41 | import { unsafeRunIE } from 'kleisli-ts/lib/unsafe'; 42 | 43 | const { impureVoid, liftK } = getInstancesFor(ioEither); 44 | 45 | const k: KleisliIO = liftK(() => { 46 | if (Math.random() > 0.5) { 47 | throw new Error('oops'); 48 | } 49 | return 'foo'; 50 | }); 51 | const log: KleisliIO = impureVoid((s) => console.log(s)); 52 | 53 | unsafeRunIE(k.andThen(log).run()); 54 | ``` 55 | -------------------------------------------------------------------------------- /examples/game.ts: -------------------------------------------------------------------------------- 1 | import { taskEither, TaskEither, URI } from 'fp-ts/lib/TaskEither'; 2 | import { createInterface } from 'readline'; 3 | 4 | import { getInstancesFor, KleisliIO } from '../src'; 5 | import { unsafeRunTE } from '../src/unsafe'; 6 | 7 | const { identity, impureVoid, liftK, point, pure, whileDo } = getInstancesFor(taskEither); 8 | 9 | const read: KleisliIO = 10 | pure( 11 | () => taskEither.fromTask(() => new Promise((resolve) => { 12 | const rl = createInterface({ 13 | input: process.stdin, 14 | output: process.stdout, 15 | }); 16 | rl.question('> ', (answer) => { 17 | rl.close(); 18 | resolve(answer); 19 | }); 20 | })), 21 | ); 22 | 23 | const log: KleisliIO = 24 | impureVoid((message: string) => console.log(message)); 25 | 26 | const random: KleisliIO = 27 | impureVoid((next: number) => Math.floor(Math.random() * next + 1)); 28 | 29 | const parse: KleisliIO = 30 | pure((s: string): TaskEither => { 31 | const i = +s; 32 | return (isNaN(i) || i % 1 !== 0) ? taskEither.throwError(new Error(`${s} is not a number`)) : taskEither.of(i); 33 | }); 34 | 35 | const isYes: KleisliIO = 36 | liftK((answer) => answer === 'y'); 37 | 38 | const readAnswer: KleisliIO = 39 | liftK((name: string) => `Do you want to continue, ${name}?`) 40 | .andThen(log) 41 | .andThen(read); 42 | 43 | const check: KleisliIO = 44 | readAnswer 45 | .andThen( 46 | whileDo(liftK((answer) => answer !== 'y' && answer !== 'n'))(readAnswer), 47 | ) 48 | .andThen(isYes); 49 | 50 | const round: KleisliIO = 51 | liftK((name: string) => `${name}, guess a number between 1 and 5`) 52 | .andThen(log) 53 | .andThen(read) 54 | .andThen(parse) 55 | .andThen( 56 | point(() => 5) 57 | .andThen(random) 58 | .second(), 59 | ) 60 | .andThen(liftK( 61 | ([guess, secret]) => guess !== secret ? 62 | `You guessed wrong. The number was: ${secret}` : 63 | `You guessed right!`, 64 | )) 65 | .andThen(log); 66 | 67 | const gameloop: KleisliIO = 68 | identity() 69 | .andThen(round.asEffect().andThen(check)) 70 | .chain((answer) => answer ? gameloop : point(() => void 0)); 71 | 72 | const game: KleisliIO = 73 | point(() => `What's your name?`) 74 | .andThen(log) 75 | .andThen(read) 76 | .andThen(gameloop); 77 | 78 | unsafeRunTE(game.run()); 79 | -------------------------------------------------------------------------------- /examples/unsafeRun.ts: -------------------------------------------------------------------------------- 1 | import { ioEither, URI as IOEitherURI } from 'fp-ts/lib/IOEither'; 2 | 3 | import { getInstancesFor, KleisliIO } from '../src'; 4 | import { unsafeRunIE } from '../src/unsafe'; 5 | 6 | const { impureVoid, liftK } = getInstancesFor(ioEither); 7 | 8 | const k: KleisliIO = liftK(() => { 9 | if (Math.random() > 0.5) { 10 | throw new Error('oops'); 11 | } 12 | return 'foo'; 13 | }); 14 | const log: KleisliIO = impureVoid((s) => console.log(s)); 15 | 16 | unsafeRunIE(k.andThen(log).run()); // 🤞🏻 hope it doesn't blow up and prints 'foo' 17 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kleisli-ts", 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 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.0.0" 14 | } 15 | }, 16 | "@babel/highlight": { 17 | "version": "7.5.0", 18 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", 19 | "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", 20 | "dev": true, 21 | "requires": { 22 | "chalk": "^2.0.0", 23 | "esutils": "^2.0.2", 24 | "js-tokens": "^4.0.0" 25 | } 26 | }, 27 | "@types/chai": { 28 | "version": "4.1.7", 29 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", 30 | "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", 31 | "dev": true 32 | }, 33 | "@types/expect": { 34 | "version": "1.20.4", 35 | "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", 36 | "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", 37 | "dev": true 38 | }, 39 | "@types/json5": { 40 | "version": "0.0.29", 41 | "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", 42 | "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", 43 | "dev": true, 44 | "optional": true 45 | }, 46 | "@types/mocha": { 47 | "version": "5.2.7", 48 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", 49 | "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", 50 | "dev": true 51 | }, 52 | "@types/node": { 53 | "version": "12.0.8", 54 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.8.tgz", 55 | "integrity": "sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==", 56 | "dev": true 57 | }, 58 | "@types/normalize-package-data": { 59 | "version": "2.4.0", 60 | "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", 61 | "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", 62 | "dev": true 63 | }, 64 | "ansi-colors": { 65 | "version": "3.2.3", 66 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", 67 | "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", 68 | "dev": true 69 | }, 70 | "ansi-regex": { 71 | "version": "3.0.0", 72 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 73 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 74 | "dev": true 75 | }, 76 | "ansi-styles": { 77 | "version": "3.2.1", 78 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 79 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 80 | "dev": true, 81 | "requires": { 82 | "color-convert": "^1.9.0" 83 | } 84 | }, 85 | "arg": { 86 | "version": "4.1.0", 87 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", 88 | "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", 89 | "dev": true 90 | }, 91 | "argparse": { 92 | "version": "1.0.10", 93 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 94 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 95 | "dev": true, 96 | "requires": { 97 | "sprintf-js": "~1.0.2" 98 | } 99 | }, 100 | "arrify": { 101 | "version": "1.0.1", 102 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 103 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 104 | "dev": true 105 | }, 106 | "assertion-error": { 107 | "version": "1.1.0", 108 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 109 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 110 | "dev": true 111 | }, 112 | "balanced-match": { 113 | "version": "1.0.0", 114 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 115 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 116 | "dev": true 117 | }, 118 | "brace-expansion": { 119 | "version": "1.1.11", 120 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 121 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 122 | "dev": true, 123 | "requires": { 124 | "balanced-match": "^1.0.0", 125 | "concat-map": "0.0.1" 126 | } 127 | }, 128 | "browser-stdout": { 129 | "version": "1.3.1", 130 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 131 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 132 | "dev": true 133 | }, 134 | "buffer-from": { 135 | "version": "1.1.1", 136 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 137 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 138 | "dev": true 139 | }, 140 | "builtin-modules": { 141 | "version": "1.1.1", 142 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 143 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", 144 | "dev": true 145 | }, 146 | "caller-callsite": { 147 | "version": "2.0.0", 148 | "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", 149 | "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", 150 | "dev": true, 151 | "requires": { 152 | "callsites": "^2.0.0" 153 | } 154 | }, 155 | "caller-path": { 156 | "version": "2.0.0", 157 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", 158 | "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", 159 | "dev": true, 160 | "requires": { 161 | "caller-callsite": "^2.0.0" 162 | } 163 | }, 164 | "callsites": { 165 | "version": "2.0.0", 166 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", 167 | "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", 168 | "dev": true 169 | }, 170 | "camelcase": { 171 | "version": "5.3.1", 172 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 173 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 174 | "dev": true 175 | }, 176 | "chai": { 177 | "version": "4.2.0", 178 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", 179 | "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", 180 | "dev": true, 181 | "requires": { 182 | "assertion-error": "^1.1.0", 183 | "check-error": "^1.0.2", 184 | "deep-eql": "^3.0.1", 185 | "get-func-name": "^2.0.0", 186 | "pathval": "^1.1.0", 187 | "type-detect": "^4.0.5" 188 | } 189 | }, 190 | "chalk": { 191 | "version": "2.4.2", 192 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 193 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 194 | "dev": true, 195 | "requires": { 196 | "ansi-styles": "^3.2.1", 197 | "escape-string-regexp": "^1.0.5", 198 | "supports-color": "^5.3.0" 199 | } 200 | }, 201 | "check-error": { 202 | "version": "1.0.2", 203 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 204 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 205 | "dev": true 206 | }, 207 | "ci-info": { 208 | "version": "2.0.0", 209 | "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", 210 | "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", 211 | "dev": true 212 | }, 213 | "cliui": { 214 | "version": "4.1.0", 215 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", 216 | "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", 217 | "dev": true, 218 | "requires": { 219 | "string-width": "^2.1.1", 220 | "strip-ansi": "^4.0.0", 221 | "wrap-ansi": "^2.0.0" 222 | } 223 | }, 224 | "code-point-at": { 225 | "version": "1.1.0", 226 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 227 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 228 | "dev": true 229 | }, 230 | "color-convert": { 231 | "version": "1.9.3", 232 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 233 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 234 | "dev": true, 235 | "requires": { 236 | "color-name": "1.1.3" 237 | } 238 | }, 239 | "color-name": { 240 | "version": "1.1.3", 241 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 242 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 243 | "dev": true 244 | }, 245 | "commander": { 246 | "version": "2.20.0", 247 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", 248 | "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", 249 | "dev": true 250 | }, 251 | "concat-map": { 252 | "version": "0.0.1", 253 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 254 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 255 | "dev": true 256 | }, 257 | "cosmiconfig": { 258 | "version": "5.2.1", 259 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", 260 | "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", 261 | "dev": true, 262 | "requires": { 263 | "import-fresh": "^2.0.0", 264 | "is-directory": "^0.3.1", 265 | "js-yaml": "^3.13.1", 266 | "parse-json": "^4.0.0" 267 | } 268 | }, 269 | "cross-spawn": { 270 | "version": "6.0.5", 271 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 272 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 273 | "dev": true, 274 | "requires": { 275 | "nice-try": "^1.0.4", 276 | "path-key": "^2.0.1", 277 | "semver": "^5.5.0", 278 | "shebang-command": "^1.2.0", 279 | "which": "^1.2.9" 280 | } 281 | }, 282 | "debug": { 283 | "version": "3.2.6", 284 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 285 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 286 | "dev": true, 287 | "requires": { 288 | "ms": "^2.1.1" 289 | } 290 | }, 291 | "decamelize": { 292 | "version": "1.2.0", 293 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 294 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 295 | "dev": true 296 | }, 297 | "deep-eql": { 298 | "version": "3.0.1", 299 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 300 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 301 | "dev": true, 302 | "requires": { 303 | "type-detect": "^4.0.0" 304 | } 305 | }, 306 | "deepmerge": { 307 | "version": "2.2.1", 308 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", 309 | "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", 310 | "dev": true, 311 | "optional": true 312 | }, 313 | "define-properties": { 314 | "version": "1.1.3", 315 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 316 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 317 | "dev": true, 318 | "requires": { 319 | "object-keys": "^1.0.12" 320 | } 321 | }, 322 | "diff": { 323 | "version": "3.5.0", 324 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 325 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 326 | "dev": true 327 | }, 328 | "emoji-regex": { 329 | "version": "7.0.3", 330 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 331 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 332 | "dev": true 333 | }, 334 | "end-of-stream": { 335 | "version": "1.4.1", 336 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", 337 | "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", 338 | "dev": true, 339 | "requires": { 340 | "once": "^1.4.0" 341 | } 342 | }, 343 | "error-ex": { 344 | "version": "1.3.2", 345 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 346 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 347 | "dev": true, 348 | "requires": { 349 | "is-arrayish": "^0.2.1" 350 | } 351 | }, 352 | "es-abstract": { 353 | "version": "1.13.0", 354 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", 355 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", 356 | "dev": true, 357 | "requires": { 358 | "es-to-primitive": "^1.2.0", 359 | "function-bind": "^1.1.1", 360 | "has": "^1.0.3", 361 | "is-callable": "^1.1.4", 362 | "is-regex": "^1.0.4", 363 | "object-keys": "^1.0.12" 364 | } 365 | }, 366 | "es-to-primitive": { 367 | "version": "1.2.0", 368 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 369 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 370 | "dev": true, 371 | "requires": { 372 | "is-callable": "^1.1.4", 373 | "is-date-object": "^1.0.1", 374 | "is-symbol": "^1.0.2" 375 | } 376 | }, 377 | "escape-string-regexp": { 378 | "version": "1.0.5", 379 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 380 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 381 | "dev": true 382 | }, 383 | "esprima": { 384 | "version": "4.0.1", 385 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 386 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 387 | "dev": true 388 | }, 389 | "esutils": { 390 | "version": "2.0.3", 391 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 392 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 393 | "dev": true 394 | }, 395 | "execa": { 396 | "version": "1.0.0", 397 | "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", 398 | "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", 399 | "dev": true, 400 | "requires": { 401 | "cross-spawn": "^6.0.0", 402 | "get-stream": "^4.0.0", 403 | "is-stream": "^1.1.0", 404 | "npm-run-path": "^2.0.0", 405 | "p-finally": "^1.0.0", 406 | "signal-exit": "^3.0.0", 407 | "strip-eof": "^1.0.0" 408 | } 409 | }, 410 | "find-up": { 411 | "version": "3.0.0", 412 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 413 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 414 | "dev": true, 415 | "requires": { 416 | "locate-path": "^3.0.0" 417 | } 418 | }, 419 | "flat": { 420 | "version": "4.1.0", 421 | "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", 422 | "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", 423 | "dev": true, 424 | "requires": { 425 | "is-buffer": "~2.0.3" 426 | } 427 | }, 428 | "fp-ts": { 429 | "version": "2.0.5", 430 | "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.0.5.tgz", 431 | "integrity": "sha512-opI5r+rVlpZE7Rhk0YtqsrmxGkbIw0dRNqGca8FEAMMnjomXotG+R9QkLQg20onx7R8qhepAn4CCOP8usma/Xw==", 432 | "dev": true 433 | }, 434 | "fs.realpath": { 435 | "version": "1.0.0", 436 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 437 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 438 | "dev": true 439 | }, 440 | "function-bind": { 441 | "version": "1.1.1", 442 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 443 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 444 | "dev": true 445 | }, 446 | "get-caller-file": { 447 | "version": "2.0.5", 448 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 449 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 450 | "dev": true 451 | }, 452 | "get-func-name": { 453 | "version": "2.0.0", 454 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 455 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 456 | "dev": true 457 | }, 458 | "get-stdin": { 459 | "version": "7.0.0", 460 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", 461 | "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", 462 | "dev": true 463 | }, 464 | "get-stream": { 465 | "version": "4.1.0", 466 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", 467 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", 468 | "dev": true, 469 | "requires": { 470 | "pump": "^3.0.0" 471 | } 472 | }, 473 | "glob": { 474 | "version": "7.1.4", 475 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", 476 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", 477 | "dev": true, 478 | "requires": { 479 | "fs.realpath": "^1.0.0", 480 | "inflight": "^1.0.4", 481 | "inherits": "2", 482 | "minimatch": "^3.0.4", 483 | "once": "^1.3.0", 484 | "path-is-absolute": "^1.0.0" 485 | } 486 | }, 487 | "growl": { 488 | "version": "1.10.5", 489 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 490 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 491 | "dev": true 492 | }, 493 | "has": { 494 | "version": "1.0.3", 495 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 496 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 497 | "dev": true, 498 | "requires": { 499 | "function-bind": "^1.1.1" 500 | } 501 | }, 502 | "has-flag": { 503 | "version": "3.0.0", 504 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 505 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 506 | "dev": true 507 | }, 508 | "has-symbols": { 509 | "version": "1.0.0", 510 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 511 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 512 | "dev": true 513 | }, 514 | "he": { 515 | "version": "1.2.0", 516 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 517 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 518 | "dev": true 519 | }, 520 | "hosted-git-info": { 521 | "version": "2.7.1", 522 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", 523 | "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", 524 | "dev": true 525 | }, 526 | "husky": { 527 | "version": "2.4.1", 528 | "resolved": "https://registry.npmjs.org/husky/-/husky-2.4.1.tgz", 529 | "integrity": "sha512-ZRwMWHr7QruR22dQ5l3rEGXQ7rAQYsJYqaeCd+NyOsIFczAtqaApZQP3P4HwLZjCtFbm3SUNYoKuoBXX3AYYfw==", 530 | "dev": true, 531 | "requires": { 532 | "cosmiconfig": "^5.2.0", 533 | "execa": "^1.0.0", 534 | "find-up": "^3.0.0", 535 | "get-stdin": "^7.0.0", 536 | "is-ci": "^2.0.0", 537 | "pkg-dir": "^4.1.0", 538 | "please-upgrade-node": "^3.1.1", 539 | "read-pkg": "^5.1.1", 540 | "run-node": "^1.0.0", 541 | "slash": "^3.0.0" 542 | } 543 | }, 544 | "import-fresh": { 545 | "version": "2.0.0", 546 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", 547 | "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", 548 | "dev": true, 549 | "requires": { 550 | "caller-path": "^2.0.0", 551 | "resolve-from": "^3.0.0" 552 | } 553 | }, 554 | "inflight": { 555 | "version": "1.0.6", 556 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 557 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 558 | "dev": true, 559 | "requires": { 560 | "once": "^1.3.0", 561 | "wrappy": "1" 562 | } 563 | }, 564 | "inherits": { 565 | "version": "2.0.3", 566 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 567 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 568 | "dev": true 569 | }, 570 | "invert-kv": { 571 | "version": "2.0.0", 572 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", 573 | "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", 574 | "dev": true 575 | }, 576 | "is-arrayish": { 577 | "version": "0.2.1", 578 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 579 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 580 | "dev": true 581 | }, 582 | "is-buffer": { 583 | "version": "2.0.3", 584 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", 585 | "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", 586 | "dev": true 587 | }, 588 | "is-callable": { 589 | "version": "1.1.4", 590 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 591 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 592 | "dev": true 593 | }, 594 | "is-ci": { 595 | "version": "2.0.0", 596 | "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", 597 | "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", 598 | "dev": true, 599 | "requires": { 600 | "ci-info": "^2.0.0" 601 | } 602 | }, 603 | "is-date-object": { 604 | "version": "1.0.1", 605 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 606 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 607 | "dev": true 608 | }, 609 | "is-directory": { 610 | "version": "0.3.1", 611 | "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", 612 | "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", 613 | "dev": true 614 | }, 615 | "is-fullwidth-code-point": { 616 | "version": "2.0.0", 617 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 618 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 619 | "dev": true 620 | }, 621 | "is-regex": { 622 | "version": "1.0.4", 623 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 624 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 625 | "dev": true, 626 | "requires": { 627 | "has": "^1.0.1" 628 | } 629 | }, 630 | "is-stream": { 631 | "version": "1.1.0", 632 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 633 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", 634 | "dev": true 635 | }, 636 | "is-symbol": { 637 | "version": "1.0.2", 638 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 639 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 640 | "dev": true, 641 | "requires": { 642 | "has-symbols": "^1.0.0" 643 | } 644 | }, 645 | "isexe": { 646 | "version": "2.0.0", 647 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 648 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 649 | "dev": true 650 | }, 651 | "js-tokens": { 652 | "version": "4.0.0", 653 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 654 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 655 | "dev": true 656 | }, 657 | "js-yaml": { 658 | "version": "3.13.1", 659 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 660 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 661 | "dev": true, 662 | "requires": { 663 | "argparse": "^1.0.7", 664 | "esprima": "^4.0.0" 665 | } 666 | }, 667 | "json-parse-better-errors": { 668 | "version": "1.0.2", 669 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 670 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 671 | "dev": true 672 | }, 673 | "json5": { 674 | "version": "1.0.1", 675 | "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", 676 | "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", 677 | "dev": true, 678 | "optional": true, 679 | "requires": { 680 | "minimist": "^1.2.0" 681 | }, 682 | "dependencies": { 683 | "minimist": { 684 | "version": "1.2.0", 685 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 686 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 687 | "dev": true, 688 | "optional": true 689 | } 690 | } 691 | }, 692 | "lcid": { 693 | "version": "2.0.0", 694 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", 695 | "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", 696 | "dev": true, 697 | "requires": { 698 | "invert-kv": "^2.0.0" 699 | } 700 | }, 701 | "locate-path": { 702 | "version": "3.0.0", 703 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 704 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 705 | "dev": true, 706 | "requires": { 707 | "p-locate": "^3.0.0", 708 | "path-exists": "^3.0.0" 709 | } 710 | }, 711 | "lodash": { 712 | "version": "4.17.14", 713 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", 714 | "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", 715 | "dev": true 716 | }, 717 | "log-symbols": { 718 | "version": "2.2.0", 719 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", 720 | "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", 721 | "dev": true, 722 | "requires": { 723 | "chalk": "^2.0.1" 724 | } 725 | }, 726 | "make-error": { 727 | "version": "1.3.5", 728 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", 729 | "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", 730 | "dev": true 731 | }, 732 | "map-age-cleaner": { 733 | "version": "0.1.3", 734 | "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", 735 | "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", 736 | "dev": true, 737 | "requires": { 738 | "p-defer": "^1.0.0" 739 | } 740 | }, 741 | "mem": { 742 | "version": "4.3.0", 743 | "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", 744 | "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", 745 | "dev": true, 746 | "requires": { 747 | "map-age-cleaner": "^0.1.1", 748 | "mimic-fn": "^2.0.0", 749 | "p-is-promise": "^2.0.0" 750 | } 751 | }, 752 | "mimic-fn": { 753 | "version": "2.1.0", 754 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 755 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 756 | "dev": true 757 | }, 758 | "minimatch": { 759 | "version": "3.0.4", 760 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 761 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 762 | "dev": true, 763 | "requires": { 764 | "brace-expansion": "^1.1.7" 765 | } 766 | }, 767 | "minimist": { 768 | "version": "0.0.8", 769 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 770 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 771 | "dev": true 772 | }, 773 | "mkdirp": { 774 | "version": "0.5.1", 775 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 776 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 777 | "dev": true, 778 | "requires": { 779 | "minimist": "0.0.8" 780 | } 781 | }, 782 | "mocha": { 783 | "version": "6.1.4", 784 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", 785 | "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", 786 | "dev": true, 787 | "requires": { 788 | "ansi-colors": "3.2.3", 789 | "browser-stdout": "1.3.1", 790 | "debug": "3.2.6", 791 | "diff": "3.5.0", 792 | "escape-string-regexp": "1.0.5", 793 | "find-up": "3.0.0", 794 | "glob": "7.1.3", 795 | "growl": "1.10.5", 796 | "he": "1.2.0", 797 | "js-yaml": "3.13.1", 798 | "log-symbols": "2.2.0", 799 | "minimatch": "3.0.4", 800 | "mkdirp": "0.5.1", 801 | "ms": "2.1.1", 802 | "node-environment-flags": "1.0.5", 803 | "object.assign": "4.1.0", 804 | "strip-json-comments": "2.0.1", 805 | "supports-color": "6.0.0", 806 | "which": "1.3.1", 807 | "wide-align": "1.1.3", 808 | "yargs": "13.2.2", 809 | "yargs-parser": "13.0.0", 810 | "yargs-unparser": "1.5.0" 811 | }, 812 | "dependencies": { 813 | "glob": { 814 | "version": "7.1.3", 815 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 816 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 817 | "dev": true, 818 | "requires": { 819 | "fs.realpath": "^1.0.0", 820 | "inflight": "^1.0.4", 821 | "inherits": "2", 822 | "minimatch": "^3.0.4", 823 | "once": "^1.3.0", 824 | "path-is-absolute": "^1.0.0" 825 | } 826 | }, 827 | "supports-color": { 828 | "version": "6.0.0", 829 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", 830 | "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", 831 | "dev": true, 832 | "requires": { 833 | "has-flag": "^3.0.0" 834 | } 835 | } 836 | } 837 | }, 838 | "ms": { 839 | "version": "2.1.1", 840 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 841 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 842 | "dev": true 843 | }, 844 | "nice-try": { 845 | "version": "1.0.5", 846 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 847 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 848 | "dev": true 849 | }, 850 | "node-environment-flags": { 851 | "version": "1.0.5", 852 | "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", 853 | "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", 854 | "dev": true, 855 | "requires": { 856 | "object.getownpropertydescriptors": "^2.0.3", 857 | "semver": "^5.7.0" 858 | } 859 | }, 860 | "normalize-package-data": { 861 | "version": "2.5.0", 862 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 863 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 864 | "dev": true, 865 | "requires": { 866 | "hosted-git-info": "^2.1.4", 867 | "resolve": "^1.10.0", 868 | "semver": "2 || 3 || 4 || 5", 869 | "validate-npm-package-license": "^3.0.1" 870 | } 871 | }, 872 | "npm-run-path": { 873 | "version": "2.0.2", 874 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 875 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 876 | "dev": true, 877 | "requires": { 878 | "path-key": "^2.0.0" 879 | } 880 | }, 881 | "number-is-nan": { 882 | "version": "1.0.1", 883 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 884 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 885 | "dev": true 886 | }, 887 | "object-keys": { 888 | "version": "1.1.1", 889 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 890 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 891 | "dev": true 892 | }, 893 | "object.assign": { 894 | "version": "4.1.0", 895 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 896 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 897 | "dev": true, 898 | "requires": { 899 | "define-properties": "^1.1.2", 900 | "function-bind": "^1.1.1", 901 | "has-symbols": "^1.0.0", 902 | "object-keys": "^1.0.11" 903 | } 904 | }, 905 | "object.getownpropertydescriptors": { 906 | "version": "2.0.3", 907 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", 908 | "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", 909 | "dev": true, 910 | "requires": { 911 | "define-properties": "^1.1.2", 912 | "es-abstract": "^1.5.1" 913 | } 914 | }, 915 | "once": { 916 | "version": "1.4.0", 917 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 918 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 919 | "dev": true, 920 | "requires": { 921 | "wrappy": "1" 922 | } 923 | }, 924 | "os-locale": { 925 | "version": "3.1.0", 926 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", 927 | "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", 928 | "dev": true, 929 | "requires": { 930 | "execa": "^1.0.0", 931 | "lcid": "^2.0.0", 932 | "mem": "^4.0.0" 933 | } 934 | }, 935 | "p-defer": { 936 | "version": "1.0.0", 937 | "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", 938 | "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", 939 | "dev": true 940 | }, 941 | "p-finally": { 942 | "version": "1.0.0", 943 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 944 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", 945 | "dev": true 946 | }, 947 | "p-is-promise": { 948 | "version": "2.1.0", 949 | "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", 950 | "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", 951 | "dev": true 952 | }, 953 | "p-limit": { 954 | "version": "2.2.0", 955 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", 956 | "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", 957 | "dev": true, 958 | "requires": { 959 | "p-try": "^2.0.0" 960 | } 961 | }, 962 | "p-locate": { 963 | "version": "3.0.0", 964 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 965 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 966 | "dev": true, 967 | "requires": { 968 | "p-limit": "^2.0.0" 969 | } 970 | }, 971 | "p-try": { 972 | "version": "2.2.0", 973 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 974 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 975 | "dev": true 976 | }, 977 | "parse-json": { 978 | "version": "4.0.0", 979 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 980 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 981 | "dev": true, 982 | "requires": { 983 | "error-ex": "^1.3.1", 984 | "json-parse-better-errors": "^1.0.1" 985 | } 986 | }, 987 | "path-exists": { 988 | "version": "3.0.0", 989 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 990 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 991 | "dev": true 992 | }, 993 | "path-is-absolute": { 994 | "version": "1.0.1", 995 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 996 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 997 | "dev": true 998 | }, 999 | "path-key": { 1000 | "version": "2.0.1", 1001 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1002 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1003 | "dev": true 1004 | }, 1005 | "path-parse": { 1006 | "version": "1.0.6", 1007 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 1008 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 1009 | "dev": true 1010 | }, 1011 | "pathval": { 1012 | "version": "1.1.0", 1013 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 1014 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 1015 | "dev": true 1016 | }, 1017 | "pkg-dir": { 1018 | "version": "4.2.0", 1019 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 1020 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 1021 | "dev": true, 1022 | "requires": { 1023 | "find-up": "^4.0.0" 1024 | }, 1025 | "dependencies": { 1026 | "find-up": { 1027 | "version": "4.0.0", 1028 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.0.0.tgz", 1029 | "integrity": "sha512-zoH7ZWPkRdgwYCDVoQTzqjG8JSPANhtvLhh4KVUHyKnaUJJrNeFmWIkTcNuJmR3GLMEmGYEf2S2bjgx26JTF+Q==", 1030 | "dev": true, 1031 | "requires": { 1032 | "locate-path": "^5.0.0" 1033 | } 1034 | }, 1035 | "locate-path": { 1036 | "version": "5.0.0", 1037 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 1038 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 1039 | "dev": true, 1040 | "requires": { 1041 | "p-locate": "^4.1.0" 1042 | } 1043 | }, 1044 | "p-locate": { 1045 | "version": "4.1.0", 1046 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 1047 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 1048 | "dev": true, 1049 | "requires": { 1050 | "p-limit": "^2.2.0" 1051 | } 1052 | } 1053 | } 1054 | }, 1055 | "please-upgrade-node": { 1056 | "version": "3.1.1", 1057 | "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz", 1058 | "integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==", 1059 | "dev": true, 1060 | "requires": { 1061 | "semver-compare": "^1.0.0" 1062 | } 1063 | }, 1064 | "pump": { 1065 | "version": "3.0.0", 1066 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1067 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1068 | "dev": true, 1069 | "requires": { 1070 | "end-of-stream": "^1.1.0", 1071 | "once": "^1.3.1" 1072 | } 1073 | }, 1074 | "read-pkg": { 1075 | "version": "5.1.1", 1076 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.1.1.tgz", 1077 | "integrity": "sha512-dFcTLQi6BZ+aFUaICg7er+/usEoqFdQxiEBsEMNGoipenihtxxtdrQuBXvyANCEI8VuUIVYFgeHGx9sLLvim4w==", 1078 | "dev": true, 1079 | "requires": { 1080 | "@types/normalize-package-data": "^2.4.0", 1081 | "normalize-package-data": "^2.5.0", 1082 | "parse-json": "^4.0.0", 1083 | "type-fest": "^0.4.1" 1084 | } 1085 | }, 1086 | "readline": { 1087 | "version": "1.3.0", 1088 | "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", 1089 | "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=", 1090 | "dev": true 1091 | }, 1092 | "require-directory": { 1093 | "version": "2.1.1", 1094 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1095 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 1096 | "dev": true 1097 | }, 1098 | "require-main-filename": { 1099 | "version": "2.0.0", 1100 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 1101 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 1102 | "dev": true 1103 | }, 1104 | "resolve": { 1105 | "version": "1.11.0", 1106 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", 1107 | "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", 1108 | "dev": true, 1109 | "requires": { 1110 | "path-parse": "^1.0.6" 1111 | } 1112 | }, 1113 | "resolve-from": { 1114 | "version": "3.0.0", 1115 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", 1116 | "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", 1117 | "dev": true 1118 | }, 1119 | "run-node": { 1120 | "version": "1.0.0", 1121 | "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", 1122 | "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", 1123 | "dev": true 1124 | }, 1125 | "semver": { 1126 | "version": "5.7.0", 1127 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 1128 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", 1129 | "dev": true 1130 | }, 1131 | "semver-compare": { 1132 | "version": "1.0.0", 1133 | "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", 1134 | "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", 1135 | "dev": true 1136 | }, 1137 | "set-blocking": { 1138 | "version": "2.0.0", 1139 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1140 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 1141 | "dev": true 1142 | }, 1143 | "shebang-command": { 1144 | "version": "1.2.0", 1145 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1146 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1147 | "dev": true, 1148 | "requires": { 1149 | "shebang-regex": "^1.0.0" 1150 | } 1151 | }, 1152 | "shebang-regex": { 1153 | "version": "1.0.0", 1154 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1155 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 1156 | "dev": true 1157 | }, 1158 | "signal-exit": { 1159 | "version": "3.0.2", 1160 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1161 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 1162 | "dev": true 1163 | }, 1164 | "slash": { 1165 | "version": "3.0.0", 1166 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", 1167 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", 1168 | "dev": true 1169 | }, 1170 | "source-map": { 1171 | "version": "0.6.1", 1172 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1173 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1174 | "dev": true 1175 | }, 1176 | "source-map-support": { 1177 | "version": "0.5.12", 1178 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", 1179 | "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", 1180 | "dev": true, 1181 | "requires": { 1182 | "buffer-from": "^1.0.0", 1183 | "source-map": "^0.6.0" 1184 | } 1185 | }, 1186 | "spdx-correct": { 1187 | "version": "3.1.0", 1188 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 1189 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 1190 | "dev": true, 1191 | "requires": { 1192 | "spdx-expression-parse": "^3.0.0", 1193 | "spdx-license-ids": "^3.0.0" 1194 | } 1195 | }, 1196 | "spdx-exceptions": { 1197 | "version": "2.2.0", 1198 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 1199 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", 1200 | "dev": true 1201 | }, 1202 | "spdx-expression-parse": { 1203 | "version": "3.0.0", 1204 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 1205 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 1206 | "dev": true, 1207 | "requires": { 1208 | "spdx-exceptions": "^2.1.0", 1209 | "spdx-license-ids": "^3.0.0" 1210 | } 1211 | }, 1212 | "spdx-license-ids": { 1213 | "version": "3.0.4", 1214 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", 1215 | "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", 1216 | "dev": true 1217 | }, 1218 | "sprintf-js": { 1219 | "version": "1.0.3", 1220 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1221 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1222 | "dev": true 1223 | }, 1224 | "string-width": { 1225 | "version": "2.1.1", 1226 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1227 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1228 | "dev": true, 1229 | "requires": { 1230 | "is-fullwidth-code-point": "^2.0.0", 1231 | "strip-ansi": "^4.0.0" 1232 | } 1233 | }, 1234 | "strip-ansi": { 1235 | "version": "4.0.0", 1236 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1237 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1238 | "dev": true, 1239 | "requires": { 1240 | "ansi-regex": "^3.0.0" 1241 | } 1242 | }, 1243 | "strip-bom": { 1244 | "version": "3.0.0", 1245 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1246 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1247 | "dev": true, 1248 | "optional": true 1249 | }, 1250 | "strip-eof": { 1251 | "version": "1.0.0", 1252 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 1253 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", 1254 | "dev": true 1255 | }, 1256 | "strip-json-comments": { 1257 | "version": "2.0.1", 1258 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1259 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1260 | "dev": true 1261 | }, 1262 | "supports-color": { 1263 | "version": "5.5.0", 1264 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1265 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1266 | "dev": true, 1267 | "requires": { 1268 | "has-flag": "^3.0.0" 1269 | } 1270 | }, 1271 | "ts-mocha": { 1272 | "version": "6.0.0", 1273 | "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-6.0.0.tgz", 1274 | "integrity": "sha512-ZCtJK8WXxHNbFNjvUKQIXZby/+ybQQkaBcM/3QhBQUfwjpdGFE9F6iWsHhF5ifQNFV/lWiOODi2VMD5AyPcQyg==", 1275 | "dev": true, 1276 | "requires": { 1277 | "ts-node": "7.0.1", 1278 | "tsconfig-paths": "^3.5.0" 1279 | }, 1280 | "dependencies": { 1281 | "minimist": { 1282 | "version": "1.2.0", 1283 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1284 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1285 | "dev": true 1286 | }, 1287 | "ts-node": { 1288 | "version": "7.0.1", 1289 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", 1290 | "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", 1291 | "dev": true, 1292 | "requires": { 1293 | "arrify": "^1.0.0", 1294 | "buffer-from": "^1.1.0", 1295 | "diff": "^3.1.0", 1296 | "make-error": "^1.1.1", 1297 | "minimist": "^1.2.0", 1298 | "mkdirp": "^0.5.1", 1299 | "source-map-support": "^0.5.6", 1300 | "yn": "^2.0.0" 1301 | } 1302 | }, 1303 | "yn": { 1304 | "version": "2.0.0", 1305 | "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", 1306 | "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", 1307 | "dev": true 1308 | } 1309 | } 1310 | }, 1311 | "ts-node": { 1312 | "version": "8.2.0", 1313 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.2.0.tgz", 1314 | "integrity": "sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw==", 1315 | "dev": true, 1316 | "requires": { 1317 | "arg": "^4.1.0", 1318 | "diff": "^4.0.1", 1319 | "make-error": "^1.1.1", 1320 | "source-map-support": "^0.5.6", 1321 | "yn": "^3.0.0" 1322 | }, 1323 | "dependencies": { 1324 | "diff": { 1325 | "version": "4.0.1", 1326 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", 1327 | "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", 1328 | "dev": true 1329 | } 1330 | } 1331 | }, 1332 | "tsconfig-paths": { 1333 | "version": "3.8.0", 1334 | "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.8.0.tgz", 1335 | "integrity": "sha512-zZEYFo4sjORK8W58ENkRn9s+HmQFkkwydDG7My5s/fnfr2YYCaiyXe/HBUcIgU8epEKOXwiahOO+KZYjiXlWyQ==", 1336 | "dev": true, 1337 | "optional": true, 1338 | "requires": { 1339 | "@types/json5": "^0.0.29", 1340 | "deepmerge": "^2.0.1", 1341 | "json5": "^1.0.1", 1342 | "minimist": "^1.2.0", 1343 | "strip-bom": "^3.0.0" 1344 | }, 1345 | "dependencies": { 1346 | "minimist": { 1347 | "version": "1.2.0", 1348 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1349 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1350 | "dev": true, 1351 | "optional": true 1352 | } 1353 | } 1354 | }, 1355 | "tslib": { 1356 | "version": "1.10.0", 1357 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 1358 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", 1359 | "dev": true 1360 | }, 1361 | "tslint": { 1362 | "version": "5.20.0", 1363 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.0.tgz", 1364 | "integrity": "sha512-2vqIvkMHbnx8acMogAERQ/IuINOq6DFqgF8/VDvhEkBqQh/x6SP0Y+OHnKth9/ZcHQSroOZwUQSN18v8KKF0/g==", 1365 | "dev": true, 1366 | "requires": { 1367 | "@babel/code-frame": "^7.0.0", 1368 | "builtin-modules": "^1.1.1", 1369 | "chalk": "^2.3.0", 1370 | "commander": "^2.12.1", 1371 | "diff": "^4.0.1", 1372 | "glob": "^7.1.1", 1373 | "js-yaml": "^3.13.1", 1374 | "minimatch": "^3.0.4", 1375 | "mkdirp": "^0.5.1", 1376 | "resolve": "^1.3.2", 1377 | "semver": "^5.3.0", 1378 | "tslib": "^1.8.0", 1379 | "tsutils": "^2.29.0" 1380 | }, 1381 | "dependencies": { 1382 | "diff": { 1383 | "version": "4.0.1", 1384 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", 1385 | "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", 1386 | "dev": true 1387 | } 1388 | } 1389 | }, 1390 | "tsutils": { 1391 | "version": "2.29.0", 1392 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 1393 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 1394 | "dev": true, 1395 | "requires": { 1396 | "tslib": "^1.8.1" 1397 | } 1398 | }, 1399 | "type-detect": { 1400 | "version": "4.0.8", 1401 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 1402 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 1403 | "dev": true 1404 | }, 1405 | "type-fest": { 1406 | "version": "0.4.1", 1407 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz", 1408 | "integrity": "sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==", 1409 | "dev": true 1410 | }, 1411 | "typescript": { 1412 | "version": "3.6.2", 1413 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.2.tgz", 1414 | "integrity": "sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==", 1415 | "dev": true 1416 | }, 1417 | "validate-npm-package-license": { 1418 | "version": "3.0.4", 1419 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 1420 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 1421 | "dev": true, 1422 | "requires": { 1423 | "spdx-correct": "^3.0.0", 1424 | "spdx-expression-parse": "^3.0.0" 1425 | } 1426 | }, 1427 | "which": { 1428 | "version": "1.3.1", 1429 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1430 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1431 | "dev": true, 1432 | "requires": { 1433 | "isexe": "^2.0.0" 1434 | } 1435 | }, 1436 | "which-module": { 1437 | "version": "2.0.0", 1438 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 1439 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 1440 | "dev": true 1441 | }, 1442 | "wide-align": { 1443 | "version": "1.1.3", 1444 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1445 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1446 | "dev": true, 1447 | "requires": { 1448 | "string-width": "^1.0.2 || 2" 1449 | } 1450 | }, 1451 | "wrap-ansi": { 1452 | "version": "2.1.0", 1453 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 1454 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 1455 | "dev": true, 1456 | "requires": { 1457 | "string-width": "^1.0.1", 1458 | "strip-ansi": "^3.0.1" 1459 | }, 1460 | "dependencies": { 1461 | "ansi-regex": { 1462 | "version": "2.1.1", 1463 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 1464 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 1465 | "dev": true 1466 | }, 1467 | "is-fullwidth-code-point": { 1468 | "version": "1.0.0", 1469 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 1470 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 1471 | "dev": true, 1472 | "requires": { 1473 | "number-is-nan": "^1.0.0" 1474 | } 1475 | }, 1476 | "string-width": { 1477 | "version": "1.0.2", 1478 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1479 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1480 | "dev": true, 1481 | "requires": { 1482 | "code-point-at": "^1.0.0", 1483 | "is-fullwidth-code-point": "^1.0.0", 1484 | "strip-ansi": "^3.0.0" 1485 | } 1486 | }, 1487 | "strip-ansi": { 1488 | "version": "3.0.1", 1489 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1490 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1491 | "dev": true, 1492 | "requires": { 1493 | "ansi-regex": "^2.0.0" 1494 | } 1495 | } 1496 | } 1497 | }, 1498 | "wrappy": { 1499 | "version": "1.0.2", 1500 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1501 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1502 | "dev": true 1503 | }, 1504 | "y18n": { 1505 | "version": "4.0.0", 1506 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 1507 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", 1508 | "dev": true 1509 | }, 1510 | "yargs": { 1511 | "version": "13.2.2", 1512 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", 1513 | "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", 1514 | "dev": true, 1515 | "requires": { 1516 | "cliui": "^4.0.0", 1517 | "find-up": "^3.0.0", 1518 | "get-caller-file": "^2.0.1", 1519 | "os-locale": "^3.1.0", 1520 | "require-directory": "^2.1.1", 1521 | "require-main-filename": "^2.0.0", 1522 | "set-blocking": "^2.0.0", 1523 | "string-width": "^3.0.0", 1524 | "which-module": "^2.0.0", 1525 | "y18n": "^4.0.0", 1526 | "yargs-parser": "^13.0.0" 1527 | }, 1528 | "dependencies": { 1529 | "ansi-regex": { 1530 | "version": "4.1.0", 1531 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 1532 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 1533 | "dev": true 1534 | }, 1535 | "string-width": { 1536 | "version": "3.1.0", 1537 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 1538 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 1539 | "dev": true, 1540 | "requires": { 1541 | "emoji-regex": "^7.0.1", 1542 | "is-fullwidth-code-point": "^2.0.0", 1543 | "strip-ansi": "^5.1.0" 1544 | } 1545 | }, 1546 | "strip-ansi": { 1547 | "version": "5.2.0", 1548 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1549 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1550 | "dev": true, 1551 | "requires": { 1552 | "ansi-regex": "^4.1.0" 1553 | } 1554 | } 1555 | } 1556 | }, 1557 | "yargs-parser": { 1558 | "version": "13.0.0", 1559 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", 1560 | "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", 1561 | "dev": true, 1562 | "requires": { 1563 | "camelcase": "^5.0.0", 1564 | "decamelize": "^1.2.0" 1565 | } 1566 | }, 1567 | "yargs-unparser": { 1568 | "version": "1.5.0", 1569 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", 1570 | "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", 1571 | "dev": true, 1572 | "requires": { 1573 | "flat": "^4.1.0", 1574 | "lodash": "^4.17.11", 1575 | "yargs": "^12.0.5" 1576 | }, 1577 | "dependencies": { 1578 | "get-caller-file": { 1579 | "version": "1.0.3", 1580 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", 1581 | "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", 1582 | "dev": true 1583 | }, 1584 | "require-main-filename": { 1585 | "version": "1.0.1", 1586 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", 1587 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", 1588 | "dev": true 1589 | }, 1590 | "yargs": { 1591 | "version": "12.0.5", 1592 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", 1593 | "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", 1594 | "dev": true, 1595 | "requires": { 1596 | "cliui": "^4.0.0", 1597 | "decamelize": "^1.2.0", 1598 | "find-up": "^3.0.0", 1599 | "get-caller-file": "^1.0.1", 1600 | "os-locale": "^3.0.0", 1601 | "require-directory": "^2.1.1", 1602 | "require-main-filename": "^1.0.1", 1603 | "set-blocking": "^2.0.0", 1604 | "string-width": "^2.0.0", 1605 | "which-module": "^2.0.0", 1606 | "y18n": "^3.2.1 || ^4.0.0", 1607 | "yargs-parser": "^11.1.1" 1608 | } 1609 | }, 1610 | "yargs-parser": { 1611 | "version": "11.1.1", 1612 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", 1613 | "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", 1614 | "dev": true, 1615 | "requires": { 1616 | "camelcase": "^5.0.0", 1617 | "decamelize": "^1.2.0" 1618 | } 1619 | } 1620 | } 1621 | }, 1622 | "yn": { 1623 | "version": "3.1.0", 1624 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", 1625 | "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", 1626 | "dev": true 1627 | } 1628 | } 1629 | } 1630 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kleisli-ts", 3 | "version": "1.0.0", 4 | "description": "Kleisli IO for TypeScript", 5 | "files": [ 6 | "lib" 7 | ], 8 | "main": "lib/index.js", 9 | "typings": "lib/index.d.ts", 10 | "scripts": { 11 | "build": "rm -rf ./lib && tsc --build tsconfig.production.json", 12 | "lint": "tsc -p ./tsconfig.json --noEmit && tslint --project ./tsconfig.json --config ./tslint.json", 13 | "test": "ts-mocha -p ./tsconfig.json src/**/*.test.ts" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/YBogomolov/kleisli-ts.git" 18 | }, 19 | "keywords": [ 20 | "kleisli", 21 | "fp", 22 | "functional", 23 | "arrow", 24 | "typescript" 25 | ], 26 | "author": "Yuriy Bogomolov ", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/YBogomolov/kleisli-ts/issues" 30 | }, 31 | "homepage": "https://github.com/YBogomolov/kleisli-ts#readme", 32 | "peerDependencies": { 33 | "fp-ts": "^2.0.0" 34 | }, 35 | "devDependencies": { 36 | "@types/chai": "^4.1.7", 37 | "@types/expect": "^1.20.4", 38 | "@types/mocha": "^5.2.7", 39 | "@types/node": "^12.0.8", 40 | "chai": "^4.2.0", 41 | "fp-ts": "^2.0.5", 42 | "husky": "^2.4.1", 43 | "mocha": "^6.1.4", 44 | "readline": "^1.3.0", 45 | "ts-mocha": "^6.0.0", 46 | "ts-node": "^8.2.0", 47 | "tslint": "^5.20.0", 48 | "typescript": "^3.6.2" 49 | }, 50 | "husky": { 51 | "hooks": { 52 | "pre-push": "npm test" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Distributes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Yuriy Bogomolov 3 | * 4 | * Licensed under the Apache LicensVersion 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { HKT, Kind, Kind2, Kind3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'; 18 | 19 | /** 20 | * Represents distributive law between two HKTs: 21 | * F> => G> 22 | */ 23 | export type Distributes = 24 | (fga: HKT>) => HKT>; 25 | export type Distributes11 = 26 | (fga: Kind>) => Kind>; 27 | export type Distributes12 = 28 | (fga: Kind>) => Kind2>; 29 | export type Distributes13 = 30 | (fga: Kind>) => Kind3>; 31 | export type Distributes21 = 32 | (fga: Kind2>) => Kind>; 33 | export type Distributes22 = 34 | (fga: Kind2>) => Kind2>; 35 | export type Distributes23 = 36 | (fga: Kind2>) => Kind3>; 37 | export type Distributes31 = 38 | (fga: Kind3>) => Kind>; 39 | export type Distributes32 = 40 | (fga: Kind3>) => Kind2>; 41 | export type Distributes33 = 42 | (fga: Kind3>) => Kind3>; 43 | -------------------------------------------------------------------------------- /src/bikleisli-io.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Yuriy Bogomolov 3 | * 4 | * Licensed under the Apache LicensVersion 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Comonad2 } from 'fp-ts/lib/Comonad'; 18 | import { Either, fold, left as eitherLeft, right as eitherRight } from 'fp-ts/lib/Either'; 19 | import { flow } from 'fp-ts/lib/function'; 20 | import { Kind2, URIS2 } from 'fp-ts/lib/HKT'; 21 | import { Monad2 } from 'fp-ts/lib/Monad'; 22 | import { pipe } from 'fp-ts/lib/pipeable'; 23 | 24 | import { Distributes22 } from './Distributes'; 25 | import { KleisliError } from './error'; 26 | 27 | /** 28 | * BiKleisliIO – an effectful function from `Kind` to `Kind2`. 29 | * For more intuition about Kleisli arrows please @see http://www.cse.chalmers.se/~rjmh/Papers/arrows.pdf 30 | * 31 | * @template E error type 32 | * @template A domain type 33 | * @template B codomain type 34 | */ 35 | export abstract class BiKleisliIO { 36 | abstract tag: 'Pure' | 'Impure' | 'Compose'; 37 | 38 | /** 39 | * Executes current `BiKleisliIO`, yielding IO of either ann error of type `E` or value of type `B`. 40 | * @param a Value of type `Kind` 41 | */ 42 | abstract run(a: Kind2): Kind2; 43 | 44 | abstract W: Comonad2; 45 | abstract M: Monad2; 46 | abstract T: Distributes22; 47 | 48 | /** 49 | * Applicative `ap` function. 50 | * Apply a lifted in `BiKleisliIO` context function to current value of `BiKleisliIO`. 51 | * @param fbc Function from `B` to `C`, lifted in the context of `BiKleisliIO` 52 | */ 53 | ap(fbc: BiKleisliIO C>): BiKleisliIO { 54 | return pure(this.W, this.M, this.T)((a) => this.M.ap(fbc.run(a), this.run(a))); 55 | } 56 | 57 | /** 58 | * Functorial `map` function. 59 | * Lift the passed function `f` into a context of `BiKleisliIO`. 60 | * @param f Function from `B` to `C` to transform the encapsulated value 61 | */ 62 | map(f: (b: B) => C): BiKleisliIO { 63 | return this.andThen(liftK(this.W, this.M, this.T)(f)); 64 | } 65 | 66 | /** 67 | * Monadic `chain` function. 68 | * Apply function `f` to the result of current `BiKleisliIO`, 69 | * determining the next flow of computations. 70 | * @param f Function from `B` to `BiKleisliIO`, which represents next sequential computation. 71 | */ 72 | chain(f: (b: B) => BiKleisliIO) { 73 | return pure(this.W, this.M, this.T)((a) => this.M.chain(this.run(a), (b) => f(b).run(a))); 74 | } 75 | 76 | /** 77 | * Compose current `BiKleisliIO` with the next one. 78 | * @param that Sequential `BiKleisliIO` computation 79 | */ 80 | andThen(that: BiKleisliIO): BiKleisliIO { 81 | return composeK(this.W, this.M, this.T)(that, this); 82 | } 83 | 84 | /** 85 | * Execute `this` and `that` computations and if both succeed, process the results with `f`. 86 | * @see both 87 | * @param that Second `BiKleisliIO` computation to run alongside with current 88 | * @param f Function to process the results of both computations 89 | */ 90 | zipWith(that: BiKleisliIO): (f: (t: [B, C]) => D) => BiKleisliIO { 91 | return (f) => zipWith(this.W, this.M, this.T)(this, that)(f); 92 | } 93 | 94 | /** 95 | * Execute `this` and `that` computations and return a tuple of results. 96 | * @see zipWith 97 | * @param that Second `BiKleisliIO` computation to run alongside with current 98 | */ 99 | both(that: BiKleisliIO): BiKleisliIO { 100 | return zipWith(this.W, this.M, this.T)(this, that)((x) => x); 101 | } 102 | 103 | /** 104 | * Depending on an input, run ether `this` or `that` computation. 105 | * @param that Alternative computation 106 | */ 107 | join(that: BiKleisliIO): BiKleisliIO, B> { 108 | return switchK(this.W, this.M, this.T)(this, that); 109 | } 110 | 111 | /** 112 | * Pass the original imput of type `A` alongside with the result of computation of type `B`, which comes *first*. 113 | */ 114 | first(): BiKleisliIO { 115 | return this.both(identity(this.W, this.M, this.T)()); 116 | } 117 | 118 | /** 119 | * Pass the original imput of type `A` alongside with the result of computation of type `B`, which comes *second*. 120 | */ 121 | second(): BiKleisliIO { 122 | return identity(this.W, this.M, this.T)().both(this); 123 | } 124 | 125 | /** 126 | * Discard the results of `this` computation and return `c`. 127 | * @param c Value of type `C` to return 128 | */ 129 | constant(c: C): BiKleisliIO { 130 | return this.andThen(liftK(this.W, this.M, this.T)(() => c)); 131 | } 132 | 133 | /** 134 | * Discard the results of `this` computation. 135 | */ 136 | toVoid(): BiKleisliIO { 137 | return this.constant(void 0); 138 | } 139 | 140 | /** 141 | * Discard the results of `this` computation and propagate the original input. 142 | * Effectively just keep the effect of `this` computation. 143 | */ 144 | asEffect(): BiKleisliIO { 145 | return this.first().andThen(snd(this.W, this.M, this.T)()); 146 | } 147 | } 148 | 149 | /** 150 | * A pure functional computation from `A` to `Kind2`, which **never** throws in runtime. 151 | * 152 | * @see Kleisli 153 | * 154 | * @template A domain type 155 | * @template B codomain type 156 | */ 157 | class Pure extends BiKleisliIO { 158 | readonly tag = 'Pure'; 159 | constructor( 160 | readonly W: Comonad2, 161 | readonly M: Monad2, 162 | readonly T: Distributes22, 163 | readonly _run: (a: Kind2) => Kind2, 164 | ) { super(); } 165 | 166 | run = (fa: Kind2): Kind2 => this._run(fa); 167 | } 168 | 169 | /** 170 | * An impure effectful computation from `A` to `B`, which may throw an exception of type `E` 171 | * 172 | * @see Kleisli 173 | * 174 | * @template A domain type 175 | * @template B codomain type 176 | */ 177 | class Impure extends BiKleisliIO { 178 | readonly tag = 'Impure'; 179 | constructor( 180 | readonly W: Comonad2, 181 | readonly M: Monad2, 182 | readonly T: Distributes22, 183 | readonly _run: (a: A) => B, 184 | ) { super(); } 185 | 186 | run = (fa: Kind2): Kind2 => this.M.of(this._run(this.W.extract(fa))); 187 | } 188 | 189 | /** 190 | * A right-to-left composition of two Kleisli functions. 191 | * 192 | * @see Kleisli 193 | * 194 | * @template A domain type 195 | * @template B codomain type 196 | */ 197 | class Compose extends BiKleisliIO { 198 | readonly tag = 'Compose'; 199 | constructor( 200 | readonly W: Comonad2, 201 | readonly M: Monad2, 202 | readonly T: Distributes22, 203 | readonly f: BiKleisliIO, 204 | readonly g: BiKleisliIO, 205 | ) { super(); } 206 | 207 | run = (fa: Kind2): Kind2 => 208 | this.M.chain(this.T(this.W.extend(fa, this.f.run)), (fb) => this.g.run(fb)) 209 | } 210 | 211 | function isImpure( 212 | a: BiKleisliIO, 213 | ): a is Impure { 214 | return a.tag === 'Impure'; 215 | } 216 | 217 | /** 218 | * Create a new instance of `Pure` computation. 219 | * @param f Function to run 220 | */ 221 | export function pure(W: Comonad2, M: Monad2, T: Distributes22) { 222 | return (f: (a: Kind2) => Kind2): BiKleisliIO => 223 | new Pure(W, M, T, f); 224 | } 225 | 226 | /** 227 | * Create a new instance of `Impure` computation. 228 | * @param catcher Function to transform the error from `Error` into `E` 229 | * @param f Impure computation from `A` to `B` which may throw 230 | */ 231 | export function impure(W: Comonad2, M: Monad2, T: Distributes22) { 232 | return (catcher: (e: Error) => E) => (f: (a: A) => B): BiKleisliIO => 233 | new Impure(W, M, T, (a: A) => { 234 | try { 235 | return f(a); 236 | } catch (error) { 237 | if (catcher(error) !== undefined) { 238 | throw new KleisliError(catcher(error)); 239 | } 240 | throw error; 241 | } 242 | }); 243 | } 244 | 245 | const voidCatcher = (e: Error): never => { throw e; }; 246 | 247 | /** 248 | * Create a new `BiKleisliIO` computation from impure function which *you know* to never throw exceptions, 249 | * or throw exceptions which should lead to termination fo the program. 250 | * @param f Impure computation from `A` to `B` 251 | */ 252 | export function impureVoid(W: Comonad2, M: Monad2, T: Distributes22) { 253 | return (f: (a: A) => B): BiKleisliIO => impure(W, M, T)(voidCatcher)(f); 254 | } 255 | 256 | /** 257 | * Lift the impure computation into `BiKleisliIO` context. 258 | * @param f Impure function from `A` to `B` 259 | */ 260 | export function liftK(W: Comonad2, M: Monad2, T: Distributes22) { 261 | return (f: (a: A) => B): BiKleisliIO => new Impure(W, M, T, f); 262 | } 263 | 264 | /** 265 | * Monadic `chain` function. 266 | * Apply function `f` to the result of current `BiKleisliIO`, determining the next flow of computations. 267 | * @param fa Basic Kleisli computation 268 | * @param f Function from `B` to `BiKleisliIO`, which represents next sequential computation 269 | */ 270 | export function chain(W: Comonad2, M: Monad2, T: Distributes22) { 271 | return ( 272 | fa: BiKleisliIO, 273 | f: (b: B) => BiKleisliIO, 274 | ): BiKleisliIO => pure(W, M, T)((a) => M.chain(fa.run(a), (b) => f(b).run(a))); 275 | } 276 | 277 | /** 278 | * Create a new `BiKleisliIO` computation which result in `b`. 279 | * @param b Lazy value of type `B` 280 | */ 281 | export function point(W: Comonad2, M: Monad2, T: Distributes22) { 282 | return (b: () => B): BiKleisliIO => liftK(W, M, T)(b); 283 | } 284 | 285 | /** 286 | * Applicative `of` function. 287 | * Lift a value of type `B` into a context of `BiKleisliIO`. 288 | * @param b Value of type `B` 289 | */ 290 | export function of(W: Comonad2, M: Monad2, T: Distributes22) { 291 | return (b: B): BiKleisliIO => liftK(W, M, T)(() => b); 292 | } 293 | 294 | /** 295 | * Tuple swap, lifted in `BiKleisliIO` context. 296 | */ 297 | export function swap(W: Comonad2, M: Monad2, T: Distributes22) { 298 | return (): BiKleisliIO => liftK(W, M, T)(([a, b]) => [b, a]); 299 | } 300 | 301 | /** 302 | * Perform right-to-left Kleisli arrows compotions. 303 | * @param second Second computation to apply 304 | * @param first First computation to apply 305 | */ 306 | export function composeK(W: Comonad2, M: Monad2, T: Distributes22) { 307 | return ( 308 | second: BiKleisliIO, 309 | first: BiKleisliIO, 310 | ): BiKleisliIO => 311 | isImpure(second) && isImpure(first) ? 312 | new Impure(W, M, T, flow(first._run, second._run)) : 313 | new Compose(W, M, T, first, second); 314 | } 315 | 316 | /** 317 | * Perform left-to-right Kleisli arrows compotions. 318 | * @param first First computation to apply 319 | * @param second Second computation to apply 320 | */ 321 | export function pipeK(W: Comonad2, M: Monad2, T: Distributes22) { 322 | return ( 323 | first: BiKleisliIO, 324 | second: BiKleisliIO, 325 | ): BiKleisliIO => 326 | composeK(W, M, T)(second, first); 327 | } 328 | 329 | /** 330 | * Depending on the input of type `Either`, execute either `l` or `r` branches. 331 | * @param l Left branch of computation 332 | * @param r Right branch of computation 333 | */ 334 | export function switchK(W: Comonad2, M: Monad2, T: Distributes22) { 335 | return ( 336 | l: BiKleisliIO, 337 | r: BiKleisliIO, 338 | ): BiKleisliIO, B> => 339 | isImpure(l) && isImpure(r) ? 340 | new Impure, B>(W, M, T, (ac) => pipe( 341 | ac, 342 | fold( 343 | (a) => l._run(a), 344 | (c) => r._run(c), 345 | )), 346 | ) : 347 | pure(W, M, T)( 348 | (fac) => pipe( 349 | fac, 350 | W.extract, 351 | fold( 352 | (a) => l.run(W.extend(fac, () => a)), 353 | (c) => r.run(W.extend(fac, () => c)), 354 | ), 355 | ), 356 | ); 357 | } 358 | 359 | /** 360 | * Execute `l` and `r` computations and if both succeed, process the results with `f`. 361 | * @param l First `BiKleisliIO` computation 362 | * @param r Second `BiKleisliIO` computation 363 | * @param f Function to process the results of both computations 364 | */ 365 | export function zipWith(W: Comonad2, M: Monad2, T: Distributes22) { 366 | return (l: BiKleisliIO, r: BiKleisliIO) => 367 | (f: (t: [B, C]) => D): BiKleisliIO => 368 | isImpure(l) && isImpure(r) ? 369 | new Impure(W, M, T, (a) => f([l._run(a), r._run(a)])) : 370 | pure(W, M, T)((a) => M.chain(l.run(a), (b) => M.map(r.run(a), (c) => f([b, c])))); 371 | } 372 | 373 | /** 374 | * Propagate the input unchanged. 375 | */ 376 | export function identity(W: Comonad2, M: Monad2, T: Distributes22) { 377 | return (): BiKleisliIO => liftK(W, M, T)((x) => x); 378 | } 379 | 380 | /** 381 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 382 | * A flipped version of @see right. 383 | * @param k Computation from `A` to `B` 384 | */ 385 | export function left(W: Comonad2, M: Monad2, T: Distributes22) { 386 | return (k: BiKleisliIO): BiKleisliIO, Either> => 387 | isImpure(k) ? 388 | new Impure(W, M, T, (ac) => pipe(ac, fold( 389 | (a) => eitherLeft(k._run(a)), 390 | (c) => eitherRight(c), 391 | ))) : 392 | pure(W, M, T)((fac) => pipe( 393 | fac, 394 | W.extract, 395 | fold( 396 | (a) => M.map(k.run(W.extend(fac, () => a)), (x) => eitherLeft(x)), 397 | (c) => M.of(eitherRight(c)), 398 | ), 399 | )); 400 | } 401 | 402 | /** 403 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 404 | * A flipped version of @see left. 405 | * @param k Computation from `A` to `B` 406 | */ 407 | export function right(W: Comonad2, M: Monad2, T: Distributes22) { 408 | return (k: BiKleisliIO): BiKleisliIO, Either> => 409 | isImpure(k) ? 410 | new Impure(W, M, T, (a) => pipe(a, fold( 411 | (l) => eitherLeft(l), 412 | (r) => eitherRight(k._run(r)), 413 | ))) : 414 | pure(W, M, T)((fca) => pipe( 415 | fca, 416 | W.extract, 417 | fold( 418 | (a) => M.of(eitherLeft(a)), 419 | (c) => M.map(k.run(W.extend(fca, () => c)), (x) => eitherRight(x)), 420 | ), 421 | )); 422 | } 423 | 424 | /** 425 | * Depending on the condition, propagate the original input through the left or right part of `Either`. 426 | * @param cond Predicate for `A` 427 | */ 428 | export function test(W: Comonad2, M: Monad2, T: Distributes22) { 429 | return (cond: BiKleisliIO): BiKleisliIO> => 430 | cond.both(identity(W, M, T)()).andThen(liftK(W, M, T)(([c, a]) => c ? eitherLeft(a) : eitherRight(a))); 431 | } 432 | 433 | /** 434 | * Depending on the condition, execute either `then` or `else`. 435 | * @param cond Predicate for `A` 436 | * @param then Computation to run if `cond` is `true` 437 | * @param else_ Computation to run if `cond` is `false` 438 | */ 439 | export function ifThenElse(W: Comonad2, M: Monad2, T: Distributes22) { 440 | return (cond: BiKleisliIO) => 441 | (then: BiKleisliIO) => (else_: BiKleisliIO): BiKleisliIO => 442 | isImpure(cond) && isImpure(then) && isImpure(else_) ? 443 | new Impure(W, M, T, (a) => cond._run(a) ? then._run(a) : else_._run(a)) : 444 | test(W, M, T)(cond).andThen(switchK(W, M, T)(then, else_)); 445 | } 446 | 447 | /** 448 | * Simplified version of @see ifThenElse without the `else` part. 449 | * @param cond Predicate for `A` 450 | * @param then Computation to run if `cond` is `true` 451 | */ 452 | export function ifThen(W: Comonad2, M: Monad2, T: Distributes22) { 453 | return (cond: BiKleisliIO) => 454 | (then: BiKleisliIO): BiKleisliIO => 455 | ifThenElse(W, M, T)(cond)(then)(identity(W, M, T)()); 456 | } 457 | 458 | /** 459 | * While-loop: run `body` until `cond` is `true`. 460 | * @param cond Predicate for `A` 461 | * @param body Computation to run continuously until `cond` is `false` 462 | */ 463 | export function whileDo(W: Comonad2, M: Monad2, T: Distributes22) { 464 | return (cond: BiKleisliIO) => 465 | (body: BiKleisliIO): BiKleisliIO => { 466 | if (isImpure(cond) && isImpure(body)) { 467 | return new Impure( 468 | W, 469 | M, 470 | T, 471 | (a0) => { 472 | let a = a0; 473 | 474 | while (cond._run(a)) { 475 | a = body._run(a); 476 | } 477 | 478 | return a; 479 | }, 480 | ); 481 | } else { 482 | const loop = (): BiKleisliIO => 483 | pure(W, M, T)((fa) => M.chain(cond.run(fa), (b) => b ? body.run(fa) : M.of(W.extract(fa)))); 484 | 485 | return loop(); 486 | } 487 | }; 488 | } 489 | 490 | /** 491 | * Lifted version of `fst` tuple function. 492 | */ 493 | export function fst(W: Comonad2, M: Monad2, T: Distributes22) { 494 | return (): BiKleisliIO => liftK(W, M, T)(([a]) => a); 495 | } 496 | 497 | /** 498 | * Lifted version of `snd` tuple function. 499 | */ 500 | export function snd(W: Comonad2, M: Monad2, T: Distributes22) { 501 | return (): BiKleisliIO => liftK(W, M, T)(([, b]) => b); 502 | } 503 | 504 | /** 505 | * Convenience method which retruns instances of Kleisli API for the given monad. 506 | * @param M Monad2 & Bifunctor instance 507 | */ 508 | export function getInstancesFor( 509 | W: Comonad2, 510 | M: Monad2, 511 | T: Distributes22, 512 | ) { 513 | return ({ 514 | /** 515 | * Applicative `of` function. 516 | * Lift a value of type `B` into a context of `BiKleisliIO`. 517 | * @param b Value of type `B` 518 | */ 519 | of: of(W, M, T), 520 | /** 521 | * Create a new instance of `Pure` computation. 522 | * @param f Function to run 523 | */ 524 | pure: pure(W, M, T), 525 | /** 526 | * Create a new instance of `Impure` computation. 527 | * @param catcher Function to transform the error from `Error` into `E` 528 | * @param f Impure computation from `A` to `B` which may throw 529 | */ 530 | impure: impure(W, M, T), 531 | /** 532 | * Create a new `BiKleisliIO` computation from impure function which *you know* to never throw exceptions, 533 | * or throw exceptions which should lead to termination fo the program. 534 | * @param f Impure computation from `A` to `B` 535 | */ 536 | impureVoid: impureVoid(W, M, T), 537 | /** 538 | * Lift the impure computation into `BiKleisliIO` context. 539 | * @param f Impure function from `A` to `B` 540 | */ 541 | liftK: liftK(W, M, T), 542 | /** 543 | * Monadic `chain` function. 544 | * Apply function `f` to the result of current `BiKleisliIO`, 545 | * determining the next flow of computations. 546 | * @param fa Basic Kleisli computation 547 | * @param f Function from `B` to `BiKleisliIO`, which represents next sequential computation 548 | */ 549 | chain: chain(W, M, T), 550 | /** 551 | * Create a new `BiKleisliIO` computation which result in `b`. 552 | * @param b Lazy value of type `B` 553 | */ 554 | point: point(W, M, T), 555 | /** 556 | * Tuple swap, lifted in `BiKleisliIO` context. 557 | */ 558 | swap: swap(W, M, T), 559 | /** 560 | * Perform right-to-left Kleisli arrows compotions. 561 | * @param second Second computation to apply 562 | * @param first First computation to apply 563 | */ 564 | composeK: composeK(W, M, T), 565 | /** 566 | * Perform left-to-right Kleisli arrows compotions. 567 | * @param first First computation to apply 568 | * @param second Second computation to apply 569 | */ 570 | pipeK: pipeK(W, M, T), 571 | /** 572 | * Depending on the input of type `Either`, execute either `l` or `r` branches. 573 | * @param l Left branch of computation 574 | * @param r Right branch of computation 575 | */ 576 | switchK: switchK(W, M, T), 577 | /** 578 | * Execute `l` and `r` computations and if both succeed, process the results with `f`. 579 | * @param l First `BiKleisliIO` computation 580 | * @param r Second `BiKleisliIO` computation 581 | * @param f Function to process the results of both computations 582 | */ 583 | zipWith: zipWith(W, M, T), 584 | /** 585 | * Propagate the input unchanged. 586 | */ 587 | identity: identity(W, M, T), 588 | /** 589 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 590 | * A flipped version of @see right. 591 | * @param k Computation from `A` to `B` 592 | */ 593 | left: left(W, M, T), 594 | /** 595 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 596 | * A flipped version of @see left. 597 | * @param k Computation from `A` to `B` 598 | */ 599 | right: right(W, M, T), 600 | /** 601 | * Depending on the condition, propagate the original input through the left or right part of `Either`. 602 | * @param cond Predicate for `A` 603 | */ 604 | test: test(W, M, T), 605 | /** 606 | * Depending on the condition, execute either `then` or `else`. 607 | * @param cond Predicate for `A` 608 | * @param then Computation to run if `cond` is `true` 609 | * @param else_ Computation to run if `cond` is `false` 610 | */ 611 | ifThenElse: ifThenElse(W, M, T), 612 | /** 613 | * Simplified version of @see ifThenElse without the `else` part. 614 | * @param cond Predicate for `A` 615 | * @param then Computation to run if `cond` is `true` 616 | */ 617 | ifThen: ifThen(W, M, T), 618 | /** 619 | * While-loop: run `body` until `cond` is `true`. 620 | * @param cond Predicate for `A` 621 | * @param body Computation to run continuously until `cond` is `false` 622 | */ 623 | whileDo: whileDo(W, M, T), 624 | /** 625 | * Lifted version of `fst` tuple function. 626 | */ 627 | fst: fst(W, M, T), 628 | /** 629 | * Lifted version of `snd` tuple function. 630 | */ 631 | snd: snd(W, M, T), 632 | }); 633 | } 634 | 635 | export default getInstancesFor; 636 | -------------------------------------------------------------------------------- /src/bikleisli.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Yuriy Bogomolov 3 | * 4 | * Licensed under the Apache LicensVersion 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Comonad1 } from 'fp-ts/lib/Comonad'; 18 | import { Either, fold, left as eitherLeft, right as eitherRight } from 'fp-ts/lib/Either'; 19 | import { flow } from 'fp-ts/lib/function'; 20 | import { Kind, URIS } from 'fp-ts/lib/HKT'; 21 | import { Monad1 } from 'fp-ts/lib/Monad'; 22 | import { pipe } from 'fp-ts/lib/pipeable'; 23 | 24 | import { Distributes11 } from './Distributes'; 25 | import { KleisliError } from './error'; 26 | 27 | /** 28 | * BiKleisli – an effectful function from `Kind` to `Kind`. 29 | * For more intuition about Kleisli arrows please @see http://www.cse.chalmers.se/~rjmh/Papers/arrows.pdf 30 | * 31 | * @template A domain type 32 | * @template B codomain type 33 | */ 34 | export abstract class BiKleisli { 35 | abstract tag: 'Pure' | 'Impure' | 'Compose'; 36 | 37 | /** 38 | * Executes current `BiKleisli`, yielding IO of either ann error of type `E` or value of type `B`. 39 | * @param a Value of type `Kind` 40 | */ 41 | abstract run(a: Kind): Kind; 42 | 43 | abstract W: Comonad1; 44 | abstract M: Monad1; 45 | abstract T: Distributes11; 46 | 47 | /** 48 | * Applicative `ap` function. 49 | * Apply a lifted in `BiKleisli` context function to current value of `BiKleisli`. 50 | * @param fbc Function from `B` to `C`, lifted in the context of `BiKleisli` 51 | */ 52 | ap(fbc: BiKleisli C>): BiKleisli { 53 | return pure(this.W, this.M, this.T)((a) => this.M.ap(fbc.run(a), this.run(a))); 54 | } 55 | 56 | /** 57 | * Functorial `map` function. 58 | * Lift the passed function `f` into a context of `BiKleisli`. 59 | * @param f Function from `B` to `C` to transform the encapsulated value 60 | */ 61 | map(f: (b: B) => C): BiKleisli { 62 | return this.andThen(liftK(this.W, this.M, this.T)(f)); 63 | } 64 | 65 | /** 66 | * Monadic `chain` function. 67 | * Apply function `f` to the result of current `BiKleisli`, determining the next flow of computations. 68 | * @param f Function from `B` to `BiKleisli`, which represents next sequential computation. 69 | */ 70 | chain(f: (b: B) => BiKleisli) { 71 | return pure(this.W, this.M, this.T)((a) => this.M.chain(this.run(a), (b) => f(b).run(a))); 72 | } 73 | 74 | /** 75 | * Compose current `BiKleisli` with the next one. 76 | * @param that Sequential `BiKleisli` computation 77 | */ 78 | andThen(that: BiKleisli): BiKleisli { 79 | return composeK(this.W, this.M, this.T)(that, this); 80 | } 81 | 82 | /** 83 | * Execute `this` and `that` computations and if both succeed, process the results with `f`. 84 | * @see both 85 | * @param that Second `BiKleisli` computation to run alongside with current 86 | * @param f Function to process the results of both computations 87 | */ 88 | zipWith(that: BiKleisli): (f: (t: [B, C]) => D) => BiKleisli { 89 | return (f) => zipWith(this.W, this.M, this.T)(this, that)(f); 90 | } 91 | 92 | /** 93 | * Execute `this` and `that` computations and return a tuple of results. 94 | * @see zipWith 95 | * @param that Second `BiKleisli` computation to run alongside with current 96 | */ 97 | both(that: BiKleisli): BiKleisli { 98 | return zipWith(this.W, this.M, this.T)(this, that)((x) => x); 99 | } 100 | 101 | /** 102 | * Depending on an input, run ether `this` or `that` computation. 103 | * @param that Alternative computation 104 | */ 105 | join(that: BiKleisli): BiKleisli, B> { 106 | return switchK(this.W, this.M, this.T)(this, that); 107 | } 108 | 109 | /** 110 | * Pass the original imput of type `A` alongside with the result of computation of type `B`, which comes *first*. 111 | */ 112 | first(): BiKleisli { 113 | return this.both(identity(this.W, this.M, this.T)()); 114 | } 115 | 116 | /** 117 | * Pass the original imput of type `A` alongside with the result of computation of type `B`, which comes *second*. 118 | */ 119 | second(): BiKleisli { 120 | return identity(this.W, this.M, this.T)().both(this); 121 | } 122 | 123 | /** 124 | * Discard the results of `this` computation and return `c`. 125 | * @param c Value of type `C` to return 126 | */ 127 | constant(c: C): BiKleisli { 128 | return this.andThen(liftK(this.W, this.M, this.T)(() => c)); 129 | } 130 | 131 | /** 132 | * Discard the results of `this` computation. 133 | */ 134 | toVoid(): BiKleisli { 135 | return this.constant(void 0); 136 | } 137 | 138 | /** 139 | * Discard the results of `this` computation and propagate the original input. 140 | * Effectively just keep the effect of `this` computation. 141 | */ 142 | asEffect(): BiKleisli { 143 | return this.first().andThen(snd(this.W, this.M, this.T)()); 144 | } 145 | } 146 | 147 | /** 148 | * A pure functional computation from `Kind` to `Kind`, which **never** throws in runtime. 149 | * 150 | * @see BiKleisli 151 | * 152 | * @template A domain type 153 | * @template B codomain type 154 | */ 155 | class Pure extends BiKleisli { 156 | readonly tag = 'Pure'; 157 | constructor( 158 | readonly W: Comonad1, 159 | readonly M: Monad1, 160 | readonly T: Distributes11, 161 | readonly _run: (a: Kind) => Kind, 162 | ) { super(); } 163 | 164 | run = (fa: Kind): Kind => this._run(fa); 165 | } 166 | 167 | /** 168 | * An impure effectful computation from `A` to `B` 169 | * 170 | * @see BiKleisli 171 | * 172 | * @template A domain type 173 | * @template B codomain type 174 | */ 175 | class Impure extends BiKleisli { 176 | readonly tag = 'Impure'; 177 | constructor( 178 | readonly W: Comonad1, 179 | readonly M: Monad1, 180 | readonly T: Distributes11, 181 | readonly _run: (a: A) => B, 182 | ) { super(); } 183 | 184 | run = (fa: Kind): Kind => this.M.of(this._run(this.W.extract(fa))); 185 | } 186 | 187 | /** 188 | * A right-to-left composition of two Kleisli functions. 189 | * 190 | * @see BiKleisli 191 | * 192 | * @template A domain type 193 | * @template B codomain type 194 | */ 195 | class Compose extends BiKleisli { 196 | readonly tag = 'Compose'; 197 | constructor( 198 | readonly W: Comonad1, 199 | readonly M: Monad1, 200 | readonly T: Distributes11, 201 | readonly f: BiKleisli, 202 | readonly g: BiKleisli, 203 | ) { super(); } 204 | 205 | run = (fa: Kind): Kind => this.M.chain(this.T(this.W.extend(fa, this.f.run)), (fb) => this.g.run(fb)); 206 | } 207 | 208 | function isImpure(a: BiKleisli): a is Impure { 209 | return a.tag === 'Impure'; 210 | } 211 | 212 | /** 213 | * Create a new instance of `Pure` computation. 214 | * @param f Function to run 215 | */ 216 | export function pure(W: Comonad1, M: Monad1, T: Distributes11) { 217 | return (f: (a: Kind) => Kind): BiKleisli => new Pure(W, M, T, f); 218 | } 219 | 220 | /** 221 | * Create a new instance of `Impure` computation. 222 | * @param catcher Function to transform the error from `Error` into `E` 223 | * @param f Impure computation from `A` to `B` which may throw 224 | */ 225 | export function impure(W: Comonad1, M: Monad1, T: Distributes11) { 226 | return (catcher: (e: Error) => E) => (f: (a: A) => B): BiKleisli => 227 | new Impure(W, M, T, (a: A) => { 228 | try { 229 | return f(a); 230 | } catch (error) { 231 | if (catcher(error) !== undefined) { 232 | throw new KleisliError(catcher(error)); 233 | } 234 | throw error; 235 | } 236 | }); 237 | } 238 | 239 | const voidCatcher = (e: Error): never => { throw e; }; 240 | 241 | /** 242 | * Create a new `BiKleisli` computation from impure function which *you know* to never throw exceptions, 243 | * or throw exceptions which should lead to termination fo the program. 244 | * @param f Impure computation from `A` to `B` 245 | */ 246 | export function impureVoid(W: Comonad1, M: Monad1, T: Distributes11) { 247 | return (f: (a: A) => B): BiKleisli => impure(W, M, T)(voidCatcher)(f); 248 | } 249 | 250 | /** 251 | * Lift the impure computation into `BiKleisli` context. 252 | * @param f Impure function from `A` to `B` 253 | */ 254 | export function liftK(W: Comonad1, M: Monad1, T: Distributes11) { 255 | return (f: (a: A) => B): BiKleisli => new Impure(W, M, T, f); 256 | } 257 | 258 | /** 259 | * Monadic `chain` function. 260 | * Apply function `f` to the result of current `BiKleisli`, determining the next flow of computations. 261 | * @param fa Basic Kleisli computation 262 | * @param f Function from `B` to `BiKleisli`, which represents next sequential computation 263 | */ 264 | export function chain(W: Comonad1, M: Monad1, T: Distributes11) { 265 | return (fa: BiKleisli, f: (b: B) => BiKleisli): BiKleisli => 266 | pure(W, M, T)((a) => M.chain(fa.run(a), (b) => f(b).run(a))); 267 | } 268 | 269 | /** 270 | * Create a new `BiKleisli` computation which result in `b`. 271 | * @param b Lazy value of type `B` 272 | */ 273 | export function point(W: Comonad1, M: Monad1, T: Distributes11) { 274 | return (b: () => B): BiKleisli => liftK(W, M, T)(b); 275 | } 276 | 277 | /** 278 | * Applicative `of` function. 279 | * Lift a value of type `B` into a context of `BiKleisli`. 280 | * @param b Value of type `B` 281 | */ 282 | export function of(W: Comonad1, M: Monad1, T: Distributes11) { 283 | return (b: B): BiKleisli => liftK(W, M, T)(() => b); 284 | } 285 | 286 | /** 287 | * Tuple swap, lifted in `BiKleisli` context. 288 | */ 289 | export function swap(W: Comonad1, M: Monad1, T: Distributes11) { 290 | return (): BiKleisli => liftK(W, M, T)(([a, b]) => [b, a]); 291 | } 292 | 293 | /** 294 | * Perform right-to-left Kleisli arrows compotions. 295 | * @param second Second computation to apply 296 | * @param first First computation to apply 297 | */ 298 | export function composeK(W: Comonad1, M: Monad1, T: Distributes11) { 299 | return (second: BiKleisli, first: BiKleisli): BiKleisli => 300 | isImpure(second) && isImpure(first) ? 301 | new Impure(W, M, T, flow(first._run, second._run)) : 302 | new Compose(W, M, T, first, second); 303 | } 304 | 305 | /** 306 | * Perform left-to-right Kleisli arrows compotions. 307 | * @param first First computation to apply 308 | * @param second Second computation to apply 309 | */ 310 | export function pipeK(W: Comonad1, M: Monad1, T: Distributes11) { 311 | return (first: BiKleisli, second: BiKleisli): BiKleisli => 312 | composeK(W, M, T)(second, first); 313 | } 314 | 315 | /** 316 | * Depending on the input of type `Either`, execute either `l` or `r` branches. 317 | * @param l Left branch of computation 318 | * @param r Right branch of computation 319 | */ 320 | export function switchK(W: Comonad1, M: Monad1, T: Distributes11) { 321 | return (l: BiKleisli, r: BiKleisli): BiKleisli, B> => 322 | isImpure(l) && isImpure(r) ? 323 | new Impure, B>(W, M, T, (ac) => pipe( 324 | ac, 325 | fold( 326 | (a) => l._run(a), 327 | (c) => r._run(c), 328 | )), 329 | ) : 330 | pure(W, M, T)( 331 | (fac) => pipe( 332 | fac, 333 | W.extract, 334 | fold( 335 | (a) => l.run(W.extend(fac, () => a)), 336 | (c) => r.run(W.extend(fac, () => c)), 337 | ), 338 | ), 339 | ); 340 | } 341 | 342 | /** 343 | * Execute `l` and `r` computations and if both succeed, process the results with `f`. 344 | * @param l First `BiKleisli` computation 345 | * @param r Second `BiKleisli` computation 346 | * @param f Function to process the results of both computations 347 | */ 348 | export function zipWith(W: Comonad1, M: Monad1, T: Distributes11) { 349 | return (l: BiKleisli, r: BiKleisli) => 350 | (f: (t: [B, C]) => D): BiKleisli => 351 | isImpure(l) && isImpure(r) ? 352 | new Impure(W, M, T, (a) => f([l._run(a), r._run(a)])) : 353 | pure(W, M, T)((a) => M.chain(l.run(a), (b) => M.map(r.run(a), (c) => f([b, c])))); 354 | } 355 | 356 | /** 357 | * Propagate the input unchanged. 358 | */ 359 | export function identity(W: Comonad1, M: Monad1, T: Distributes11) { 360 | return (): BiKleisli => liftK(W, M, T)((x) => x); 361 | } 362 | 363 | /** 364 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 365 | * A flipped version of @see right. 366 | * @param k Computation from `A` to `B` 367 | */ 368 | export function left(W: Comonad1, M: Monad1, T: Distributes11) { 369 | return (k: BiKleisli): BiKleisli, Either> => 370 | isImpure(k) ? 371 | new Impure(W, M, T, (ac) => pipe(ac, fold( 372 | (a) => eitherLeft(k._run(a)), 373 | (c) => eitherRight(c), 374 | ))) : 375 | pure(W, M, T)((fac) => pipe( 376 | fac, 377 | W.extract, 378 | fold( 379 | (a) => M.map(k.run(W.extend(fac, () => a)), (x) => eitherLeft(x)), 380 | (c) => M.of(eitherRight(c)), 381 | ), 382 | )); 383 | } 384 | 385 | /** 386 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 387 | * A flipped version of @see left. 388 | * @param k Computation from `A` to `B` 389 | */ 390 | export function right(W: Comonad1, M: Monad1, T: Distributes11) { 391 | return (k: BiKleisli): BiKleisli, Either> => 392 | isImpure(k) ? 393 | new Impure(W, M, T, (a) => pipe(a, fold( 394 | (l) => eitherLeft(l), 395 | (r) => eitherRight(k._run(r)), 396 | ))) : 397 | pure(W, M, T)((fca) => pipe( 398 | fca, 399 | W.extract, 400 | fold( 401 | (a) => M.of(eitherLeft(a)), 402 | (c) => M.map(k.run(W.extend(fca, () => c)), (x) => eitherRight(x)), 403 | ), 404 | )); 405 | } 406 | 407 | /** 408 | * Depending on the condition, propagate the original input through the left or right part of `Either`. 409 | * @param cond Predicate for `A` 410 | */ 411 | export function test(W: Comonad1, M: Monad1, T: Distributes11) { 412 | return (cond: BiKleisli): BiKleisli> => 413 | cond.both(identity(W, M, T)()).andThen(liftK(W, M, T)(([c, a]) => c ? eitherLeft(a) : eitherRight(a))); 414 | } 415 | 416 | /** 417 | * Depending on the condition, execute either `then` or `else`. 418 | * @param cond Predicate for `A` 419 | * @param then Computation to run if `cond` is `true` 420 | * @param else_ Computation to run if `cond` is `false` 421 | */ 422 | export function ifThenElse(W: Comonad1, M: Monad1, T: Distributes11) { 423 | return (cond: BiKleisli) => 424 | (then: BiKleisli) => (else_: BiKleisli): BiKleisli => 425 | isImpure(cond) && isImpure(then) && isImpure(else_) ? 426 | new Impure(W, M, T, (a) => cond._run(a) ? then._run(a) : else_._run(a)) : 427 | test(W, M, T)(cond).andThen(switchK(W, M, T)(then, else_)); 428 | } 429 | 430 | /** 431 | * Simplified version of @see ifThenElse without the `else` part. 432 | * @param cond Predicate for `A` 433 | * @param then Computation to run if `cond` is `true` 434 | */ 435 | export function ifThen(W: Comonad1, M: Monad1, T: Distributes11) { 436 | return (cond: BiKleisli) => 437 | (then: BiKleisli): BiKleisli => ifThenElse(W, M, T)(cond)(then)(identity(W, M, T)()); 438 | } 439 | 440 | /** 441 | * While-loop: run `body` until `cond` is `true`. 442 | * @param cond Predicate for `A` 443 | * @param body Computation to run continuously until `cond` is `false` 444 | */ 445 | export function whileDo(W: Comonad1, M: Monad1, T: Distributes11) { 446 | return (cond: BiKleisli) => (body: BiKleisli): BiKleisli => { 447 | if (isImpure(cond) && isImpure(body)) { 448 | return new Impure( 449 | W, 450 | M, 451 | T, 452 | (a0) => { 453 | let a = a0; 454 | 455 | while (cond._run(a)) { 456 | a = body._run(a); 457 | } 458 | 459 | return a; 460 | }, 461 | ); 462 | } else { 463 | const loop = (): BiKleisli => 464 | pure(W, M, T)((fa) => M.chain(cond.run(fa), (b) => b ? body.run(fa) : M.of(W.extract(fa)))); 465 | 466 | return loop(); 467 | } 468 | }; 469 | } 470 | 471 | /** 472 | * Lifted version of `fst` tuple function. 473 | */ 474 | export function fst(W: Comonad1, M: Monad1, T: Distributes11) { 475 | return (): BiKleisli => liftK(W, M, T)(([a]) => a); 476 | } 477 | 478 | /** 479 | * Lifted version of `snd` tuple function. 480 | */ 481 | export function snd(W: Comonad1, M: Monad1, T: Distributes11) { 482 | return (): BiKleisli => liftK(W, M, T)(([, b]) => b); 483 | } 484 | 485 | /** 486 | * Convenience method which retruns instances of Kleisli API for the given monad. 487 | * @param M Monad1 & Bifunctor instance 488 | */ 489 | export function getInstancesFor( 490 | W: Comonad1, 491 | M: Monad1, 492 | T: Distributes11, 493 | ) { 494 | return ({ 495 | /** 496 | * Applicative `of` function. 497 | * Lift a value of type `B` into a context of `BiKleisli`. 498 | * @param b Value of type `B` 499 | */ 500 | of: of(W, M, T), 501 | /** 502 | * Create a new instance of `Pure` computation. 503 | * @param f Function to run 504 | */ 505 | pure: pure(W, M, T), 506 | /** 507 | * Create a new instance of `Impure` computation. 508 | * @param catcher Function to transform the error from `Error` into `E` 509 | * @param f Impure computation from `A` to `B` which may throw 510 | */ 511 | impure: impure(W, M, T), 512 | /** 513 | * Create a new `BiKleisli` computation from impure function which *you know* to never throw exceptions, 514 | * or throw exceptions which should lead to termination fo the program. 515 | * @param f Impure computation from `A` to `B` 516 | */ 517 | impureVoid: impureVoid(W, M, T), 518 | /** 519 | * Lift the impure computation into `BiKleisli` context. 520 | * @param f Impure function from `A` to `B` 521 | */ 522 | liftK: liftK(W, M, T), 523 | /** 524 | * Monadic `chain` function. 525 | * Apply function `f` to the result of current `BiKleisli`, determining the next flow of computations. 526 | * @param fa Basic Kleisli computation 527 | * @param f Function from `B` to `BiKleisli`, which represents next sequential computation 528 | */ 529 | chain: chain(W, M, T), 530 | /** 531 | * Create a new `BiKleisli` computation which result in `b`. 532 | * @param b Lazy value of type `B` 533 | */ 534 | point: point(W, M, T), 535 | /** 536 | * Tuple swap, lifted in `BiKleisli` context. 537 | */ 538 | swap: swap(W, M, T), 539 | /** 540 | * Perform right-to-left Kleisli arrows compotions. 541 | * @param second Second computation to apply 542 | * @param first First computation to apply 543 | */ 544 | composeK: composeK(W, M, T), 545 | /** 546 | * Perform left-to-right Kleisli arrows compotions. 547 | * @param first First computation to apply 548 | * @param second Second computation to apply 549 | */ 550 | pipeK: pipeK(W, M, T), 551 | /** 552 | * Depending on the input of type `Either`, execute either `l` or `r` branches. 553 | * @param l Left branch of computation 554 | * @param r Right branch of computation 555 | */ 556 | switchK: switchK(W, M, T), 557 | /** 558 | * Execute `l` and `r` computations and if both succeed, process the results with `f`. 559 | * @param l First `BiKleisli` computation 560 | * @param r Second `BiKleisli` computation 561 | * @param f Function to process the results of both computations 562 | */ 563 | zipWith: zipWith(W, M, T), 564 | /** 565 | * Propagate the input unchanged. 566 | */ 567 | identity: identity(W, M, T), 568 | /** 569 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 570 | * A flipped version of @see right. 571 | * @param k Computation from `A` to `B` 572 | */ 573 | left: left(W, M, T), 574 | /** 575 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 576 | * A flipped version of @see left. 577 | * @param k Computation from `A` to `B` 578 | */ 579 | right: right(W, M, T), 580 | /** 581 | * Depending on the condition, propagate the original input through the left or right part of `Either`. 582 | * @param cond Predicate for `A` 583 | */ 584 | test: test(W, M, T), 585 | /** 586 | * Depending on the condition, execute either `then` or `else`. 587 | * @param cond Predicate for `A` 588 | * @param then Computation to run if `cond` is `true` 589 | * @param else_ Computation to run if `cond` is `false` 590 | */ 591 | ifThenElse: ifThenElse(W, M, T), 592 | /** 593 | * Simplified version of @see ifThenElse without the `else` part. 594 | * @param cond Predicate for `A` 595 | * @param then Computation to run if `cond` is `true` 596 | */ 597 | ifThen: ifThen(W, M, T), 598 | /** 599 | * While-loop: run `body` until `cond` is `true`. 600 | * @param cond Predicate for `A` 601 | * @param body Computation to run continuously until `cond` is `false` 602 | */ 603 | whileDo: whileDo(W, M, T), 604 | /** 605 | * Lifted version of `fst` tuple function. 606 | */ 607 | fst: fst(W, M, T), 608 | /** 609 | * Lifted version of `snd` tuple function. 610 | */ 611 | snd: snd(W, M, T), 612 | }); 613 | } 614 | 615 | export default getInstancesFor; 616 | -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Yuriy Bogomolov 3 | * 4 | * Licensed under the Apache LicensVersion 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Specialized error type for Kleisli 19 | */ 20 | export class KleisliError extends Error { 21 | constructor(readonly error: E) { super(String(error)); } 22 | } 23 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './kleisli-io'; 2 | -------------------------------------------------------------------------------- /src/kleisli-io.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Yuriy Bogomolov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | // tslint:disable:no-unused-expression 17 | 18 | import { expect } from 'chai'; 19 | import { either, fold, isLeft, isRight, left, Left, right, Right, URI } from 'fp-ts/lib/Either'; 20 | import { flow, identity } from 'fp-ts/lib/function'; 21 | 22 | import { getInstancesFor, KleisliIO } from './kleisli-io'; 23 | 24 | const K = getInstancesFor(either); 25 | 26 | describe('BiKleisli suite', () => { 27 | describe('Functor laws', () => { 28 | it('should preserve identity', () => { 29 | // fmal id ≡ id 30 | const fa: KleisliIO = K.pure((a) => a ? right(String(a)) : left(String(a))); 31 | const fa1: KleisliIO = fa.map(identity); 32 | 33 | expect(fa.run(true)).to.deep.equal(fa1.run(true)); 34 | expect(fa.run(false)).to.deep.equal(fa1.run(false)); 35 | }); 36 | 37 | it('should preserve composition of morphisms', () => { 38 | // fmap (f . g) ≡ fmap f . fmap g 39 | const fa: KleisliIO = K.pure((a) => a ? right(String(a)) : left(String(a))); 40 | 41 | const f = (a: string) => `${a}!`; 42 | const g = (a: string) => a.length; 43 | 44 | const f_o_g = flow(f, g); 45 | 46 | const fa1 = fa.map(f).map(g); 47 | const fa2 = fa.map(f_o_g); 48 | 49 | expect(fa1.run(true)).to.deep.equal(fa2.run(true)); 50 | expect(fa1.run(false)).to.deep.equal(fa2.run(false)); 51 | }); 52 | }); 53 | 54 | describe('Monad laws', () => { 55 | it('left identity', () => { 56 | // return a >>= f ≡ f a 57 | const f = (a: number) => K.of(String(a)); 58 | 59 | expect(K.of(42).chain(f).run()).to.deep.equal(f(42).run()); 60 | }); 61 | 62 | it('right identity', () => { 63 | // m >>= return ≡ m 64 | const m = K.pure(() => right(42)); 65 | 66 | expect(m.chain(K.of).run()).to.deep.equal(m.run()); 67 | }); 68 | 69 | it('associativity', () => { 70 | // (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g) 71 | const m = K.of(42); 72 | const f = (a: number) => K.of(String(a)); 73 | const g = (s: string) => K.of(s.length); 74 | 75 | expect(m.chain(f).chain(g).run()).to.deep.equal(m.chain((n) => f(n).chain(g)).run()); 76 | }); 77 | }); 78 | 79 | describe('BiKleisli API', () => { 80 | it('of', () => { 81 | const m = K.of(42); 82 | 83 | expect(isRight(m.run())).to.be.true; 84 | expect((m.run() as Right).right).to.equal(42); 85 | }); 86 | 87 | it('pure', () => { 88 | const m = K.pure( 89 | (s: string) => s.length > 0 ? right(s.toLocaleUpperCase() + '!') : left(new Error('empty string')), 90 | ); 91 | 92 | expect(isRight(m.run('aaaa'))).to.be.true; 93 | expect((m.run('aaaa') as Right).right).to.equal('AAAA!'); 94 | expect(isLeft(m.run(''))).to.be.true; 95 | expect((m.run('') as Left).left) 96 | .to.be.an.instanceOf(Error) 97 | .and 98 | .to.have.property('message').equal('empty string'); 99 | }); 100 | 101 | it('impure', () => { 102 | const f = (s: string) => { 103 | if (s.length === 0) { 104 | throw new Error('empty string'); 105 | } 106 | return s.toLocaleUpperCase() + '!'; 107 | }; 108 | const m = K.impure(identity)(f); 109 | 110 | expect(isRight(m.run('aaaa'))).to.be.true; 111 | expect((m.run('aaaa') as Right).right).to.equal('AAAA!'); 112 | expect(isLeft(m.run(''))).to.be.true; 113 | expect((m.run('') as Left).left) 114 | .to.be.an.instanceOf(Error) 115 | .and 116 | .to.have.property('message').equal('empty string'); 117 | }); 118 | 119 | it('impureVoid', () => { 120 | const f = (terminate: boolean) => { 121 | if (terminate) { 122 | throw new Error('terminate'); 123 | } 124 | return true; 125 | }; 126 | const m = K.impureVoid(f); 127 | 128 | try { 129 | expect((m.run(false) as Right).right).to.be.true; 130 | m.run(true); 131 | expect.fail(); 132 | } catch (e) { 133 | expect(e).to.be.an.instanceOf(Error); 134 | expect(e).to.have.property('message').equal('terminate'); 135 | } 136 | }); 137 | 138 | it('liftK', () => { 139 | const f = (s: string) => s.length; 140 | const m = K.liftK(f); 141 | 142 | expect(isRight(m.run(''))).to.be.true; 143 | expect((m.run('') as Right).right).to.equal(0); 144 | expect(isRight(m.run('aaaa'))).to.be.true; 145 | expect((m.run('aaaa') as Right).right).to.equal(4); 146 | }); 147 | 148 | it('chain', () => { 149 | const f = (s: string) => s.length > 0 ? 150 | K.of(s.length) : 151 | K.fail(new Error('empty string')); 152 | 153 | expect(isRight(K.of('aaa').chain(f).run())).to.be.true; 154 | expect(K.of('aaa').chain(f).run()).to.deep.equal(right(3)); 155 | expect(isLeft(K.of('').chain(f).run())).to.be.true; 156 | expect((K.of('').chain(f).run() as Left).left) 157 | .to.be.an.instanceOf(Error) 158 | .and 159 | .to.have.property('message').equal('empty string'); 160 | }); 161 | 162 | it('point', () => { 163 | const m = K.point(() => 42); 164 | 165 | expect(isRight(m.run())).to.be.true; 166 | expect(m.run()).to.deep.equal(right(42)); 167 | }); 168 | 169 | it('fail', () => { 170 | const m = K.fail(new Error('fail')); 171 | 172 | fold( 173 | (l) => expect(l).to.be.an.instanceOf(Error).and.to.have.property('message').equal('fail'), 174 | () => expect.fail('is right'), 175 | )(m.run({})); 176 | }); 177 | 178 | it('swap', () => { 179 | expect(K.swap().run([1, true])).to.deep.equal(right([true, 1])); 180 | }); 181 | 182 | it('composeK', () => { 183 | const f = K.pure( 184 | (s) => s.length > 0 ? right(s.toLocaleUpperCase() + '!') : left(new Error('empty string')), 185 | ); 186 | const g = K.pure( 187 | (s) => s.length > 0 ? right(s.length) : left(new Error('empty string')), 188 | ); 189 | 190 | fold( 191 | () => expect.fail('is left'), 192 | (r) => expect(r).to.equal(4), 193 | )(K.composeK(g, f).run('aaa')); 194 | fold( 195 | (l) => expect(l).to.be.an.instanceOf(Error).and.to.have.property('message').equal('empty string'), 196 | () => expect.fail('is right'), 197 | )(K.composeK(g, f).run('')); 198 | }); 199 | 200 | it('pipeK', () => { 201 | const f = K.pure( 202 | (s) => s.length > 0 ? right(s.toLocaleUpperCase() + '!') : left(new Error('empty string')), 203 | ); 204 | const g = K.pure( 205 | (s) => s.length > 0 ? right(s.length) : left(new Error('empty string')), 206 | ); 207 | 208 | fold( 209 | () => expect.fail('is left'), 210 | (r) => expect(r).to.equal(4), 211 | )(K.pipeK(f, g).run('aaa')); 212 | fold( 213 | (l) => expect(l).to.be.an.instanceOf(Error).and.to.have.property('message').equal('empty string'), 214 | () => expect.fail('is right'), 215 | )(K.pipeK(f, g).run('')); 216 | }); 217 | 218 | it('switchK', () => { 219 | const m = K.switchK( 220 | K.liftK((s) => s.length > 0), 221 | K.liftK((n) => n > 0), 222 | ); 223 | 224 | fold( 225 | () => expect.fail('is left'), 226 | (r) => expect(r).to.be.true, 227 | )(m.run(right(42))); 228 | fold( 229 | () => expect.fail('is left'), 230 | (r) => expect(r).to.be.false, 231 | )(m.run(left(''))); 232 | }); 233 | 234 | it('zipWith', () => { 235 | const m = K.zipWith( 236 | K.liftK((s) => s.startsWith('a')), 237 | K.liftK((s) => s.endsWith('!')), 238 | )(([startsWithA, endsWithBang]) => { 239 | switch (true) { 240 | case startsWithA && endsWithBang: 241 | return 'String starts with "a" and ends with "!"'; 242 | case startsWithA: 243 | return 'String starts with "a"'; 244 | case endsWithBang: 245 | return 'String ends with "!"'; 246 | default: 247 | return 'String neither starts with "a", nor ends with "!"'; 248 | } 249 | }); 250 | 251 | fold( 252 | () => expect.fail('is left'), 253 | (r) => expect(r).to.equal('String starts with "a"'), 254 | )(m.run('a')); 255 | fold( 256 | () => expect.fail('is left'), 257 | (r) => expect(r).to.equal('String starts with "a" and ends with "!"'), 258 | )(m.run('a!')); 259 | fold( 260 | () => expect.fail('is left'), 261 | (r) => expect(r).to.equal('String neither starts with "a", nor ends with "!"'), 262 | )(m.run('foo')); 263 | fold( 264 | () => expect.fail('is left'), 265 | (r) => expect(r).to.equal('String ends with "!"'), 266 | )(m.run('foo!')); 267 | }); 268 | 269 | it('identity', () => { 270 | expect(isRight(K.identity().run(42))).to.be.true; 271 | expect(K.identity().run(42)).to.deep.equal(right(42)); 272 | }); 273 | 274 | it('left', () => { 275 | const m = K.left(K.liftK((n) => n.toString())); 276 | expect(isRight(m.run(right(42)))).to.be.true; 277 | expect(m.run(right(42))).to.deep.equal(right(right(42))); 278 | expect(isRight(m.run(left(41)))).to.be.true; 279 | expect(m.run(left(41))).to.deep.equal(right(left('41'))); 280 | }); 281 | 282 | it('right', () => { 283 | const m = K.right(K.liftK((n) => n.toString())); 284 | expect(isRight(m.run(right(42)))).to.be.true; 285 | expect(m.run(right(42))).to.deep.equal(right(right('42'))); 286 | expect(isRight(m.run(left(41)))).to.be.true; 287 | expect(m.run(left(41))).to.deep.equal(right(left(41))); 288 | }); 289 | 290 | it('test', () => { 291 | const m = K.test(K.liftK((n) => n % 2 === 0)); 292 | 293 | fold( 294 | () => expect.fail('is right'), 295 | (r) => expect(r).to.deep.equal(left(42)), 296 | )(m.run(42)); 297 | fold( 298 | () => expect.fail('is left'), 299 | (r) => expect(r).to.deep.equal(right(41)), 300 | )(m.run(41)); 301 | }); 302 | 303 | it('ifThenElse', () => { 304 | const m = K.ifThenElse 305 | (K.liftK((n) => n % 2 === 0)) 306 | (K.liftK((n) => `is even: ${n}`)) 307 | (K.liftK((n) => `is odd: ${n}`)); 308 | 309 | expect(isRight(m.run(42))).to.be.true; 310 | expect(m.run(42)).to.deep.equal(right('is even: 42')); 311 | expect(isRight(m.run(41))).to.be.true; 312 | expect(m.run(41)).to.deep.equal(right('is odd: 41')); 313 | }); 314 | 315 | it('ifThen', () => { 316 | const m = K.ifThen 317 | (K.liftK((n) => n % 2 === 1)) 318 | (K.liftK((n) => n + 1)); 319 | 320 | expect(isRight(m.run(41))).to.be.true; 321 | expect(m.run(41)).to.deep.equal(right(42)); 322 | expect(isRight(m.run(42))).to.be.true; 323 | expect(m.run(42)).to.deep.equal(right(42)); 324 | }); 325 | 326 | it('whileDo', () => { 327 | let callCount = 0; 328 | const m = K.whileDo 329 | (K.liftK((n) => n < 10)) 330 | (K.liftK((n) => { 331 | callCount++; 332 | return n + 1; 333 | })); 334 | 335 | const res = m.run(4); 336 | 337 | expect(isRight(res)).to.be.true; 338 | expect(res).to.deep.equal(right(10)); 339 | expect(callCount).to.equal(6); 340 | }); 341 | 342 | it('fst', () => { 343 | expect(isRight(K.fst().run([1, true]))).to.be.true; 344 | expect(K.fst().run([1, true])).to.deep.equal(right(1)); 345 | }); 346 | 347 | it('snd', () => { 348 | expect(isRight(K.snd().run([1, true]))).to.be.true; 349 | expect(K.snd().run([1, true])).to.deep.equal(right(true)); 350 | }); 351 | }); 352 | }); 353 | -------------------------------------------------------------------------------- /src/kleisli-io.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-line-length 2 | /* 3 | * Copyright 2019 Yuriy Bogomolov 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * TypeScript port of John A. De Goes's talk about KleisliIO at LambdaConf'18: 18 | * https://www.youtube.com/watch?v=L8AEj6IRNEE 19 | * with additional instances of Applicative, Bifunctor, etc. 20 | * Original implementation in Scala can be found in this commit: 21 | * https://github.com/zio/zio/blob/c5c3f47c163c7638886205fefbadf43f7553751e/shared/src/main/scala/scalaz/effect/KleisliIO.scala 22 | */ 23 | // tslint:enable:max-line-length 24 | 25 | import { Bifunctor2 } from 'fp-ts/lib/Bifunctor'; 26 | import { Either, fold, left as eitherLeft, right as eitherRight } from 'fp-ts/lib/Either'; 27 | import { flow } from 'fp-ts/lib/function'; 28 | import { Kind2, URIS2 } from 'fp-ts/lib/HKT'; 29 | import { MonadThrow2 } from 'fp-ts/lib/MonadThrow'; 30 | import { pipe } from 'fp-ts/lib/pipeable'; 31 | 32 | import { KleisliError } from './error'; 33 | 34 | /** 35 | * KleisliIO – an effectful function from `A` to `Kind2`. 36 | * For more intuition about Kleisli arrows please @see http://www.cse.chalmers.se/~rjmh/Papers/arrows.pdf 37 | * 38 | * @template A domain type 39 | * @template E error type of codomain 40 | * @template B value type of codomain 41 | */ 42 | export abstract class KleisliIO { 43 | abstract tag: 'Pure' | 'Impure' | 'Compose'; 44 | 45 | /** 46 | * Executes current `KleisliIO`, yielding IO of either ann error of type `E` or value of type `B`. 47 | * @param a Value of type `A` 48 | */ 49 | abstract run(a: A): Kind2; 50 | 51 | abstract M: MonadThrow2 & Bifunctor2; 52 | 53 | /** 54 | * Applicative `ap` function. 55 | * Apply a lifted in `KleisliIO` context function to current value of `KleisliIO`. 56 | * @param fbc Function from `B` to `C`, lifted in the context of `KleisliIO` 57 | */ 58 | ap(fbc: KleisliIO C>): KleisliIO { 59 | return pure(this.M)((a) => this.M.ap(fbc.run(a), this.run(a))); 60 | } 61 | 62 | /** 63 | * Functorial `map` function. 64 | * Lift the passed function `f` into a context of `KleisliIO`. 65 | * @param f Function from `B` to `C` to transform the encapsulated value 66 | */ 67 | map(f: (b: B) => C): KleisliIO { 68 | return this.andThen(liftK(this.M)(f)); 69 | } 70 | 71 | /** 72 | * Monadic `chain` function. 73 | * Apply function `f` to the result of current `KleisliIO`, determining the next flow of computations. 74 | * @param f Function from `B` to `KleisliIO`, which represents next sequential computation. 75 | */ 76 | chain(f: (b: B) => KleisliIO) { 77 | return pure(this.M)((a) => this.M.chain(this.run(a), (b) => f(b).run(a))); 78 | } 79 | 80 | /** 81 | * Bifunctorial `bimap` function. 82 | * Take two functions to transform both error and value parts simultaneously. 83 | * @param f Function to transform the error part 84 | * @param g Function to transform the value part 85 | */ 86 | bimap(f: (e: E) => E1, g: (b: B) => C): KleisliIO { 87 | return pure(this.M)((a) => this.M.bimap(this.run(a), f, g)); 88 | } 89 | 90 | /** 91 | * Compose current `KleisliIO` with the next one. 92 | * @param that Sequential `KleisliIO` computation 93 | */ 94 | andThen(that: KleisliIO): KleisliIO { 95 | return composeK(this.M)(that, this); 96 | } 97 | 98 | /** 99 | * Execute `this` and `that` computations and if both succeed, process the results with `f`. 100 | * @see both 101 | * @param that Second `KleisliIO` computation to run alongside with current 102 | * @param f Function to process the results of both computations 103 | */ 104 | zipWith(that: KleisliIO): (f: (t: [B, C]) => D) => KleisliIO { 105 | return (f) => zipWith(this.M)(this, that)(f); 106 | } 107 | 108 | /** 109 | * Execute `this` and `that` computations and return a tuple of results. 110 | * @see zipWith 111 | * @param that Second `KleisliIO` computation to run alongside with current 112 | */ 113 | both(that: KleisliIO): KleisliIO { 114 | return zipWith(this.M)(this, that)((x) => x); 115 | } 116 | 117 | /** 118 | * Depending on an input, run ether `this` or `that` computation. 119 | * @param that Alternative computation 120 | */ 121 | join(that: KleisliIO): KleisliIO, B> { 122 | return switchK(this.M)(this, that); 123 | } 124 | 125 | /** 126 | * Pass the original imput of type `A` alongside with the result of computation of type `B`, which comes *first*. 127 | */ 128 | first(): KleisliIO { 129 | return this.both(identity(this.M)()); 130 | } 131 | 132 | /** 133 | * Pass the original imput of type `A` alongside with the result of computation of type `B`, which comes *second*. 134 | */ 135 | second(): KleisliIO { 136 | return identity(this.M)().both(this); 137 | } 138 | 139 | /** 140 | * Discard the results of `this` computation and return `c`. 141 | * @param c Value of type `C` to return 142 | */ 143 | constant(c: C): KleisliIO { 144 | return this.andThen(liftK(this.M)(() => c)); 145 | } 146 | 147 | /** 148 | * Discard the results of `this` computation. 149 | */ 150 | toVoid(): KleisliIO { 151 | return this.constant(void 0); 152 | } 153 | 154 | /** 155 | * Discard the results of `this` computation and propagate the original input. 156 | * Effectively just keep the effect of `this` computation. 157 | */ 158 | asEffect(): KleisliIO { 159 | return this.first().andThen(snd(this.M)()); 160 | } 161 | } 162 | 163 | /** 164 | * A pure functional computation from `A` to `Kind2`, which **never** throws in runtime. 165 | * 166 | * @see KleisliIO 167 | * 168 | * @template A domain type 169 | * @template E error type of codomain 170 | * @template B value type of codomain 171 | */ 172 | class Pure extends KleisliIO { 173 | readonly tag = 'Pure'; 174 | constructor(readonly M: MonadThrow2 & Bifunctor2, readonly _run: (a: A) => Kind2) { super(); } 175 | 176 | run = (a: A): Kind2 => this._run(a); 177 | } 178 | 179 | /** 180 | * An impure effectful computation from `A` to `B`, which may throw an exception of type `E` 181 | * 182 | * @see KleisliIO 183 | * 184 | * @template A domain type 185 | * @template E error type of codomain 186 | * @template B value type of codomain 187 | */ 188 | class Impure extends KleisliIO { 189 | readonly tag = 'Impure'; 190 | constructor(readonly M: MonadThrow2 & Bifunctor2, readonly _run: (a: A) => B) { super(); } 191 | 192 | run = (a: A): Kind2 => { 193 | try { 194 | const b = this._run(a); 195 | return this.M.of(b); 196 | } catch (e) { 197 | if (e instanceof KleisliError) { 198 | return this.M.throwError(e.error); 199 | } 200 | throw e; 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * A right-to-left composition of two KleisliIO functions. 207 | * 208 | * @see KleisliIO 209 | * 210 | * @template A domain type 211 | * @template E error type of codomain 212 | * @template B value type of codomain 213 | */ 214 | class Compose extends KleisliIO { 215 | readonly tag = 'Compose'; 216 | constructor( 217 | readonly M: MonadThrow2 & Bifunctor2, 218 | readonly g: KleisliIO, 219 | readonly f: KleisliIO, 220 | ) { super(); } 221 | 222 | run = (a: A): Kind2 => this.M.chain(this.f.run(a), this.g.run); 223 | } 224 | 225 | function isImpure(a: KleisliIO): a is Impure { 226 | return a.tag === 'Impure'; 227 | } 228 | 229 | /** 230 | * Create a new instance of `Pure` computation. 231 | * @param f Function to run 232 | */ 233 | export function pure(M: MonadThrow2 & Bifunctor2) { 234 | return (f: (a: A) => Kind2): KleisliIO => new Pure(M, f); 235 | } 236 | 237 | /** 238 | * Create a new instance of `Impure` computation. 239 | * @param catcher Function to transform the error from `Error` into `E` 240 | * @param f Impure computation from `A` to `B` which may throw 241 | */ 242 | export function impure(M: MonadThrow2 & Bifunctor2) { 243 | return (catcher: (e: Error) => E) => (f: (a: A) => B): KleisliIO => new Impure( 244 | M, 245 | (a: A) => { 246 | try { 247 | return f(a); 248 | } catch (error) { 249 | if (catcher(error) !== undefined) { 250 | throw new KleisliError(catcher(error)); 251 | } 252 | throw error; 253 | } 254 | }, 255 | ); 256 | } 257 | 258 | const voidCatcher = (e: Error): never => { throw e; }; 259 | 260 | /** 261 | * Create a new `KleisliIO` computation from impure function which *you know* to never throw exceptions, 262 | * or throw exceptions which should lead to termination fo the program. 263 | * @param f Impure computation from `A` to `B` 264 | */ 265 | export function impureVoid(M: MonadThrow2 & Bifunctor2) { 266 | return (f: (a: A) => B): KleisliIO => impure(M)(voidCatcher)(f); 267 | } 268 | 269 | /** 270 | * Lift the impure computation into `KleisliIO` context. 271 | * @param f Impure function from `A` to `B` 272 | */ 273 | export function liftK(M: MonadThrow2 & Bifunctor2) { 274 | return (f: (a: A) => B): KleisliIO => new Impure(M, f); 275 | } 276 | 277 | /** 278 | * Monadic `chain` function. 279 | * Apply function `f` to the result of current `KleisliIO`, determining the next flow of computations. 280 | * @param fa Basic KleisliIO computation 281 | * @param f Function from `B` to `KleisliIO`, which represents next sequential computation 282 | */ 283 | export function chain(M: MonadThrow2 & Bifunctor2) { 284 | return (fa: KleisliIO, f: (b: B) => KleisliIO): KleisliIO => 285 | pure(M)((a) => M.chain(fa.run(a), (b) => f(b).run(a))); 286 | } 287 | 288 | /** 289 | * Create a new `KleisliIO` computation which result in `b`. 290 | * @param b Lazy value of type `B` 291 | */ 292 | export function point(M: MonadThrow2 & Bifunctor2) { 293 | return (b: () => B): KleisliIO => liftK(M)(b); 294 | } 295 | 296 | /** 297 | * Applicative `of` function. 298 | * Lift a value of type `B` into a context of `KleisliIO`. 299 | * @param b Value of type `B` 300 | */ 301 | export function of(M: MonadThrow2 & Bifunctor2) { 302 | return (b: B): KleisliIO => liftK(M)(() => b); 303 | } 304 | 305 | /** 306 | * Fail with an error of type `E`. 307 | * @param e Error of type `E` 308 | */ 309 | export function fail(M: MonadThrow2 & Bifunctor2) { 310 | return (e: E): KleisliIO => new Impure(M, () => { throw new KleisliError(e); }); 311 | } 312 | 313 | /** 314 | * Tuple swap, lifted in `KleisliIO` context. 315 | */ 316 | export function swap(M: MonadThrow2 & Bifunctor2) { 317 | return (): KleisliIO => liftK(M)(([a, b]) => [b, a]); 318 | } 319 | 320 | /** 321 | * Perform right-to-left Kleisli arrows compotions. 322 | * @param second Second computation to apply 323 | * @param first First computation to apply 324 | */ 325 | export function composeK(M: MonadThrow2 & Bifunctor2) { 326 | return (second: KleisliIO, first: KleisliIO): KleisliIO => 327 | isImpure(second) && isImpure(first) ? 328 | new Impure(M, flow(first._run, second._run)) : 329 | new Compose(M, second, first); 330 | } 331 | 332 | /** 333 | * Perform left-to-right Kleisli arrows compotions. 334 | * @param first First computation to apply 335 | * @param second Second computation to apply 336 | */ 337 | export function pipeK(M: MonadThrow2 & Bifunctor2) { 338 | return (first: KleisliIO, second: KleisliIO): KleisliIO => 339 | composeK(M)(second, first); 340 | } 341 | 342 | /** 343 | * Depending on the input of type `Either`, execute either `l` or `r` branches. 344 | * @param l Left branch of computation 345 | * @param r Right branch of computation 346 | */ 347 | export function switchK(M: MonadThrow2 & Bifunctor2) { 348 | return (l: KleisliIO, r: KleisliIO): KleisliIO, B> => 349 | isImpure(l) && isImpure(r) ? 350 | new Impure, B>(M, (a) => pipe( 351 | a, 352 | fold( 353 | (al) => l._run(al), 354 | (ar) => r._run(ar), 355 | )), 356 | ) : 357 | pure(M)((a) => pipe( 358 | a, 359 | fold( 360 | (al) => l.run(al), 361 | (ar) => r.run(ar), 362 | )), 363 | ); 364 | } 365 | 366 | /** 367 | * Execute `l` and `r` computations and if both succeed, process the results with `f`. 368 | * @param l First `KleisliIO` computation 369 | * @param r Second `KleisliIO` computation 370 | * @param f Function to process the results of both computations 371 | */ 372 | export function zipWith(M: MonadThrow2 & Bifunctor2) { 373 | return (l: KleisliIO, r: KleisliIO) => 374 | (f: (t: [B, C]) => D): KleisliIO => 375 | isImpure(l) && isImpure(r) ? 376 | new Impure(M, (a) => f([l._run(a), r._run(a)])) : 377 | pure(M)((a) => M.chain(l.run(a), (b) => M.map(r.run(a), (c) => f([b, c])))); 378 | } 379 | 380 | /** 381 | * Propagate the input unchanged. 382 | */ 383 | export function identity(M: MonadThrow2 & Bifunctor2) { 384 | return (): KleisliIO => liftK(M)((x) => x); 385 | } 386 | 387 | /** 388 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 389 | * A flipped version of @see right. 390 | * @param k Computation from `A` to `B` 391 | */ 392 | export function left(M: MonadThrow2 & Bifunctor2) { 393 | return (k: KleisliIO): KleisliIO, Either> => 394 | isImpure(k) ? 395 | new Impure(M, (a) => pipe(a, fold( 396 | (l) => eitherLeft(k._run(l)), 397 | (r) => eitherRight(r), 398 | ))) : 399 | pure(M)((a) => pipe(a, fold( 400 | (l) => M.map(k.run(l), (x) => eitherLeft(x)), 401 | (r) => M.of(eitherRight(r)), 402 | ))); 403 | } 404 | 405 | /** 406 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 407 | * A flipped version of @see left. 408 | * @param k Computation from `A` to `B` 409 | */ 410 | export function right(M: MonadThrow2 & Bifunctor2) { 411 | return (k: KleisliIO): KleisliIO, Either> => 412 | isImpure(k) ? 413 | new Impure(M, (a) => pipe(a, fold( 414 | (l) => eitherLeft(l), 415 | (r) => eitherRight(k._run(r)), 416 | ))) : 417 | pure(M)((a) => pipe(a, fold( 418 | (l) => M.of(eitherLeft(l)), 419 | (r) => M.map(k.run(r), (x) => eitherRight(x)), 420 | ))); 421 | } 422 | 423 | /** 424 | * Depending on the condition, propagate the original input through the left or right part of `Either`. 425 | * @param cond Predicate for `A` 426 | */ 427 | export function test(M: MonadThrow2 & Bifunctor2) { 428 | return (cond: KleisliIO): KleisliIO> => 429 | cond.both(identity(M)()).andThen(liftK(M)(([c, a]) => c ? eitherLeft(a) : eitherRight(a))); 430 | } 431 | 432 | /** 433 | * Depending on the condition, execute either `then` or `else`. 434 | * @param cond Predicate for `A` 435 | * @param then Computation to run if `cond` is `true` 436 | * @param else_ Computation to run if `cond` is `false` 437 | */ 438 | export function ifThenElse(M: MonadThrow2 & Bifunctor2) { 439 | return (cond: KleisliIO) => 440 | (then: KleisliIO) => (else_: KleisliIO): KleisliIO => 441 | isImpure(cond) && isImpure(then) && isImpure(else_) ? 442 | new Impure(M, (a) => cond._run(a) ? then._run(a) : else_._run(a)) : 443 | test(M)(cond).andThen(switchK(M)(then, else_)); 444 | } 445 | 446 | /** 447 | * Simplified version of @see ifThenElse without the `else` part. 448 | * @param cond Predicate for `A` 449 | * @param then Computation to run if `cond` is `true` 450 | */ 451 | export function ifThen(M: MonadThrow2 & Bifunctor2) { 452 | return (cond: KleisliIO) => 453 | (then: KleisliIO): KleisliIO => ifThenElse(M)(cond)(then)(identity(M)()); 454 | } 455 | 456 | /** 457 | * While-loop: run `body` until `cond` is `true`. 458 | * @param cond Predicate for `A` 459 | * @param body Computation to run continuously until `cond` is `false` 460 | */ 461 | export function whileDo(M: MonadThrow2 & Bifunctor2) { 462 | return (cond: KleisliIO) => (body: KleisliIO): KleisliIO => { 463 | if (isImpure(cond) && isImpure(body)) { 464 | return new Impure( 465 | M, 466 | (a0) => { 467 | let a = a0; 468 | 469 | while (cond._run(a)) { 470 | a = body._run(a); 471 | } 472 | 473 | return a; 474 | }, 475 | ); 476 | } else { 477 | const loop = (): KleisliIO => 478 | pure(M)((a) => M.chain(cond.run(a), (b) => b ? M.chain(body.run(a), loop().run) : M.of(a))); 479 | 480 | return loop(); 481 | } 482 | }; 483 | } 484 | 485 | /** 486 | * Lifted version of `fst` tuple function. 487 | */ 488 | export function fst(M: MonadThrow2 & Bifunctor2) { 489 | return (): KleisliIO => liftK(M)(([a]) => a); 490 | } 491 | 492 | /** 493 | * Lifted version of `snd` tuple function. 494 | */ 495 | export function snd(M: MonadThrow2 & Bifunctor2) { 496 | return (): KleisliIO => liftK(M)(([, b]) => b); 497 | } 498 | 499 | /** 500 | * Convenience method which retruns instances of KleisliIO API for the given monad. 501 | * @param M MonadThrow & Bifunctor instance 502 | */ 503 | export function getInstancesFor(M: MonadThrow2 & Bifunctor2) { 504 | return ({ 505 | /** 506 | * Applicative `of` function. 507 | * Lift a value of type `B` into a context of `KleisliIO`. 508 | * @param b Value of type `B` 509 | */ 510 | of: of(M), 511 | /** 512 | * Create a new instance of `Pure` computation. 513 | * @param f Function to run 514 | */ 515 | pure: pure(M), 516 | /** 517 | * Create a new instance of `Impure` computation. 518 | * @param catcher Function to transform the error from `Error` into `E` 519 | * @param f Impure computation from `A` to `B` which may throw 520 | */ 521 | impure: impure(M), 522 | /** 523 | * Create a new `KleisliIO` computation from impure function which *you know* to never throw exceptions, 524 | * or throw exceptions which should lead to termination fo the program. 525 | * @param f Impure computation from `A` to `B` 526 | */ 527 | impureVoid: impureVoid(M), 528 | /** 529 | * Lift the impure computation into `KleisliIO` context. 530 | * @param f Impure function from `A` to `B` 531 | */ 532 | liftK: liftK(M), 533 | /** 534 | * Monadic `chain` function. 535 | * Apply function `f` to the result of current `KleisliIO`, determining the next flow of computations. 536 | * @param fa Basic KleisliIO computation 537 | * @param f Function from `B` to `KleisliIO`, which represents next sequential computation 538 | */ 539 | chain: chain(M), 540 | /** 541 | * Create a new `KleisliIO` computation which result in `b`. 542 | * @param b Lazy value of type `B` 543 | */ 544 | point: point(M), 545 | /** 546 | * Fail with an error of type `E`. 547 | * @param e Error of type `E` 548 | */ 549 | fail: fail(M), 550 | /** 551 | * Tuple swap, lifted in `KleisliIO` context. 552 | */ 553 | swap: swap(M), 554 | /** 555 | * Perform right-to-left Kleisli arrows compotions. 556 | * @param second Second computation to apply 557 | * @param first First computation to apply 558 | */ 559 | composeK: composeK(M), 560 | /** 561 | * Perform left-to-right Kleisli arrows compotions. 562 | * @param first First computation to apply 563 | * @param second Second computation to apply 564 | */ 565 | pipeK: pipeK(M), 566 | /** 567 | * Depending on the input of type `Either`, execute either `l` or `r` branches. 568 | * @param l Left branch of computation 569 | * @param r Right branch of computation 570 | */ 571 | switchK: switchK(M), 572 | /** 573 | * Execute `l` and `r` computations and if both succeed, process the results with `f`. 574 | * @param l First `KleisliIO` computation 575 | * @param r Second `KleisliIO` computation 576 | * @param f Function to process the results of both computations 577 | */ 578 | zipWith: zipWith(M), 579 | /** 580 | * Propagate the input unchanged. 581 | */ 582 | identity: identity(M), 583 | /** 584 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 585 | * A flipped version of @see right. 586 | * @param k Computation from `A` to `B` 587 | */ 588 | left: left(M), 589 | /** 590 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 591 | * A flipped version of @see left. 592 | * @param k Computation from `A` to `B` 593 | */ 594 | right: right(M), 595 | /** 596 | * Depending on the condition, propagate the original input through the left or right part of `Either`. 597 | * @param cond Predicate for `A` 598 | */ 599 | test: test(M), 600 | /** 601 | * Depending on the condition, execute either `then` or `else`. 602 | * @param cond Predicate for `A` 603 | * @param then Computation to run if `cond` is `true` 604 | * @param else_ Computation to run if `cond` is `false` 605 | */ 606 | ifThenElse: ifThenElse(M), 607 | /** 608 | * Simplified version of @see ifThenElse without the `else` part. 609 | * @param cond Predicate for `A` 610 | * @param then Computation to run if `cond` is `true` 611 | */ 612 | ifThen: ifThen(M), 613 | /** 614 | * While-loop: run `body` until `cond` is `true`. 615 | * @param cond Predicate for `A` 616 | * @param body Computation to run continuously until `cond` is `false` 617 | */ 618 | whileDo: whileDo(M), 619 | /** 620 | * Lifted version of `fst` tuple function. 621 | */ 622 | fst: fst(M), 623 | /** 624 | * Lifted version of `snd` tuple function. 625 | */ 626 | snd: snd(M), 627 | }); 628 | } 629 | 630 | export default getInstancesFor; 631 | -------------------------------------------------------------------------------- /src/kleisli.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Yuriy Bogomolov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | // tslint:disable:no-unused-expression 17 | 18 | import { expect } from 'chai'; 19 | import { fold, left, right } from 'fp-ts/lib/Either'; 20 | import { flow, identity as id } from 'fp-ts/lib/function'; 21 | import { identity, URI } from 'fp-ts/lib/Identity'; 22 | 23 | import { getInstancesFor, Kleisli } from './kleisli'; 24 | 25 | const K = getInstancesFor(identity); 26 | 27 | describe('Kleisli suite', () => { 28 | describe('Functor laws', () => { 29 | it('should preserve identity', () => { 30 | // fmal id ≡ id 31 | const fa: Kleisli = K.pure((a) => identity.of(String(a))); 32 | const fa1: Kleisli = fa.map(id); 33 | 34 | expect(fa.run(true)).to.deep.equal(fa1.run(true)); 35 | expect(fa.run(false)).to.deep.equal(fa1.run(false)); 36 | }); 37 | 38 | it('should preserve composition of morphisms', () => { 39 | // fmap (f . g) ≡ fmap f . fmap g 40 | const fa: Kleisli = K.pure((a) => identity.of(String(a))); 41 | 42 | const f = (a: string) => `${a}!`; 43 | const g = (a: string) => a.length; 44 | 45 | const f_o_g = flow(f, g); 46 | 47 | const fa1 = fa.map(f).map(g); 48 | const fa2 = fa.map(f_o_g); 49 | 50 | expect(fa1.run(true)).to.deep.equal(fa2.run(true)); 51 | expect(fa1.run(false)).to.deep.equal(fa2.run(false)); 52 | }); 53 | }); 54 | 55 | describe('Monad laws', () => { 56 | it('left identity', () => { 57 | // return a >>= f ≡ f a 58 | const f = (a: number) => K.of(String(a)); 59 | 60 | expect(K.of(42).chain(f).run()).to.deep.equal(f(42).run()); 61 | }); 62 | 63 | it('right identity', () => { 64 | // m >>= return ≡ m 65 | const m = K.pure(() => identity.of(42)); 66 | 67 | expect(m.chain(K.of).run()).to.deep.equal(m.run()); 68 | }); 69 | 70 | it('associativity', () => { 71 | // (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g) 72 | const m = K.of(42); 73 | const f = (a: number) => K.of(String(a)); 74 | const g = (s: string) => K.of(s.length); 75 | 76 | expect(m.chain(f).chain(g).run()).to.deep.equal(m.chain((n) => f(n).chain(g)).run()); 77 | }); 78 | }); 79 | 80 | describe('Kleisli API', () => { 81 | it('of', () => { 82 | const m = K.of(42); 83 | 84 | expect(m.run()).to.equal(42); 85 | }); 86 | 87 | it('pure', () => { 88 | const m = K.pure( 89 | (s: string) => identity.of(s.toLocaleUpperCase() + '!'), 90 | ); 91 | 92 | expect(m.run('aaaa')).to.equal('AAAA!'); 93 | }); 94 | 95 | it('impure', () => { 96 | const f = (s: string) => { 97 | if (s.length === 0) { 98 | throw new Error('empty string'); 99 | } 100 | return s.toLocaleUpperCase() + '!'; 101 | }; 102 | const m = K.impure(id)(f); 103 | 104 | expect(m.run('aaaa')).to.equal('AAAA!'); 105 | expect(() => m.run('')).throws('empty string'); 106 | }); 107 | 108 | it('impureVoid', () => { 109 | const f = (terminate: boolean) => { 110 | if (terminate) { 111 | throw new Error('terminate'); 112 | } 113 | return true; 114 | }; 115 | const m = K.impureVoid(f); 116 | 117 | try { 118 | expect(m.run(false)).to.be.true; 119 | m.run(true); 120 | expect.fail(); 121 | } catch (e) { 122 | expect(e).to.be.an.instanceOf(Error).and.to.have.property('message').equal('terminate'); 123 | } 124 | }); 125 | 126 | it('liftK', () => { 127 | const f = (s: string) => s.length; 128 | const m = K.liftK(f); 129 | 130 | expect(m.run('')).to.equal(0); 131 | expect(m.run('aaaa')).to.equal(4); 132 | }); 133 | 134 | it('chain', () => { 135 | const f = (s: string) => K.of(s.length); 136 | 137 | expect(K.of('aaa').chain(f).run()).to.equal(3); 138 | }); 139 | 140 | it('point', () => { 141 | const m = K.point(() => 42); 142 | 143 | expect(m.run()).to.equal(42); 144 | }); 145 | 146 | it('swap', () => { 147 | expect(K.swap().run([1, true])).to.deep.equal([true, 1]); 148 | }); 149 | 150 | it('flowK', () => { 151 | const f = K.pure((s) => identity.of(s.toLocaleUpperCase() + '!')); 152 | const g = K.pure((s) => identity.of(s.length)); 153 | 154 | expect(K.composeK(g, f).run('aaa')).to.equal(4); 155 | }); 156 | 157 | it('pipeK', () => { 158 | const f = K.pure((s) => identity.of(s.toLocaleUpperCase() + '!')); 159 | const g = K.pure((s) => identity.of(s.length)); 160 | 161 | expect(K.pipeK(f, g).run('aaa')).to.equal(4); 162 | }); 163 | 164 | it('switchK', () => { 165 | const m = K.switchK( 166 | K.liftK((s) => s.length > 0), 167 | K.liftK((n) => n > 0), 168 | ); 169 | 170 | expect(m.run(right(42))).to.be.true; 171 | expect(m.run(left(''))).to.be.false; 172 | }); 173 | 174 | it('zipWith', () => { 175 | const m = K.zipWith( 176 | K.liftK((s) => s.startsWith('a')), 177 | K.liftK((s) => s.endsWith('!')), 178 | )(([startsWithA, endsWithBang]) => { 179 | switch (true) { 180 | case startsWithA && endsWithBang: 181 | return 'String starts with "a" and ends with "!"'; 182 | case startsWithA: 183 | return 'String starts with "a"'; 184 | case endsWithBang: 185 | return 'String ends with "!"'; 186 | default: 187 | return 'String neither starts with "a", nor ends with "!"'; 188 | } 189 | }); 190 | 191 | expect(m.run('a')).to.equal('String starts with "a"'); 192 | expect(m.run('a!')).to.equal('String starts with "a" and ends with "!"'); 193 | expect(m.run('foo')).to.equal('String neither starts with "a", nor ends with "!"'); 194 | expect(m.run('foo!')).to.equal('String ends with "!"'); 195 | }); 196 | 197 | it('identity', () => { 198 | expect(K.identity().run(42)).to.equal(42); 199 | }); 200 | 201 | it('left', () => { 202 | const m = K.left(K.liftK((n) => n.toString())); 203 | expect(m.run(right(42))).to.deep.equal(right(42)); 204 | expect(m.run(left(41))).to.deep.equal(left('41')); 205 | }); 206 | 207 | it('right', () => { 208 | const m = K.right(K.liftK((n) => n.toString())); 209 | expect(m.run(right(42))).to.deep.equal(right('42')); 210 | expect(m.run(left(41))).to.deep.equal(left(41)); 211 | }); 212 | 213 | it('test', () => { 214 | const m = K.test(K.liftK((n) => n % 2 === 0)); 215 | 216 | fold( 217 | (l) => expect(l).to.equal(42), 218 | () => expect.fail('is right'), 219 | )(m.run(42)); 220 | fold( 221 | () => expect.fail('is left'), 222 | (r) => expect(r).to.equal(41), 223 | )(m.run(41)); 224 | }); 225 | 226 | it('ifThenElse', () => { 227 | const m = K.ifThenElse 228 | (K.liftK((n) => n % 2 === 0)) 229 | (K.liftK((n) => `is even: ${n}`)) 230 | (K.liftK((n) => `is odd: ${n}`)); 231 | 232 | expect(m.run(42)).to.equal('is even: 42'); 233 | expect(m.run(41)).to.equal('is odd: 41'); 234 | }); 235 | 236 | it('ifThen', () => { 237 | const m = K.ifThen 238 | (K.liftK((n) => n % 2 === 1)) 239 | (K.liftK((n) => n + 1)); 240 | 241 | expect(m.run(41)).to.equal(42); 242 | expect(m.run(42)).to.equal(42); 243 | }); 244 | 245 | it('whileDo', () => { 246 | let callCount = 0; 247 | const m = K.whileDo 248 | (K.liftK((n) => n < 10)) 249 | (K.liftK((n) => { 250 | callCount++; 251 | return n + 1; 252 | })); 253 | 254 | const res = m.run(4); 255 | 256 | expect(res).to.equal(10); 257 | expect(callCount).to.equal(6); 258 | }); 259 | 260 | it('fst', () => { 261 | expect(K.fst().run([1, true])).to.equal(1); 262 | }); 263 | 264 | it('snd', () => { 265 | expect(K.snd().run([1, true])).to.be.true; 266 | }); 267 | }); 268 | }); 269 | -------------------------------------------------------------------------------- /src/kleisli.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Yuriy Bogomolov 3 | * 4 | * Licensed under the Apache LicensVersion 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Either, fold, left as eitherLeft, right as eitherRight } from 'fp-ts/lib/Either'; 18 | import { flow } from 'fp-ts/lib/function'; 19 | import { Kind, URIS } from 'fp-ts/lib/HKT'; 20 | import { Monad1 } from 'fp-ts/lib/Monad'; 21 | import { pipe } from 'fp-ts/lib/pipeable'; 22 | 23 | import { KleisliError } from './error'; 24 | 25 | /** 26 | * Kleisli – an effectful function from `A` to `Kind`. 27 | * For more intuition about Kleisli arrows please @see http://www.cse.chalmers.se/~rjmh/Papers/arrows.pdf 28 | * 29 | * @template A domain type 30 | * @template B codomain type 31 | */ 32 | export abstract class Kleisli { 33 | abstract tag: 'Pure' | 'Impure' | 'Compose'; 34 | 35 | /** 36 | * Executes current `Kleisli`, yielding IO of either ann error of type `E` or value of type `B`. 37 | * @param a Value of type `A` 38 | */ 39 | abstract run(a: A): Kind; 40 | 41 | abstract M: Monad1; 42 | 43 | /** 44 | * Applicative `ap` function. 45 | * Apply a lifted in `Kleisli` context function to current value of `Kleisli`. 46 | * @param fbc Function from `B` to `C`, lifted in the context of `Kleisli` 47 | */ 48 | ap(fbc: Kleisli C>): Kleisli { 49 | return pure(this.M)((a) => this.M.ap(fbc.run(a), this.run(a))); 50 | } 51 | 52 | /** 53 | * Functorial `map` function. 54 | * Lift the passed function `f` into a context of `Kleisli`. 55 | * @param f Function from `B` to `C` to transform the encapsulated value 56 | */ 57 | map(f: (b: B) => C): Kleisli { 58 | return this.andThen(liftK(this.M)(f)); 59 | } 60 | 61 | /** 62 | * Monadic `chain` function. 63 | * Apply function `f` to the result of current `Kleisli`, determining the next flow of computations. 64 | * @param f Function from `B` to `Kleisli`, which represents next sequential computation. 65 | */ 66 | chain(f: (b: B) => Kleisli) { 67 | return pure(this.M)((a) => this.M.chain(this.run(a), (b) => f(b).run(a))); 68 | } 69 | 70 | /** 71 | * Compose current `Kleisli` with the next one. 72 | * @param that Sequential `Kleisli` computation 73 | */ 74 | andThen(that: Kleisli): Kleisli { 75 | return composeK(this.M)(that, this); 76 | } 77 | 78 | /** 79 | * Execute `this` and `that` computations and if both succeed, process the results with `f`. 80 | * @see both 81 | * @param that Second `Kleisli` computation to run alongside with current 82 | * @param f Function to process the results of both computations 83 | */ 84 | zipWith(that: Kleisli): (f: (t: [B, C]) => D) => Kleisli { 85 | return (f) => zipWith(this.M)(this, that)(f); 86 | } 87 | 88 | /** 89 | * Execute `this` and `that` computations and return a tuple of results. 90 | * @see zipWith 91 | * @param that Second `Kleisli` computation to run alongside with current 92 | */ 93 | both(that: Kleisli): Kleisli { 94 | return zipWith(this.M)(this, that)((x) => x); 95 | } 96 | 97 | /** 98 | * Depending on an input, run ether `this` or `that` computation. 99 | * @param that Alternative computation 100 | */ 101 | join(that: Kleisli): Kleisli, B> { 102 | return switchK(this.M)(this, that); 103 | } 104 | 105 | /** 106 | * Pass the original imput of type `A` alongside with the result of computation of type `B`, which comes *first*. 107 | */ 108 | first(): Kleisli { 109 | return this.both(identity(this.M)()); 110 | } 111 | 112 | /** 113 | * Pass the original imput of type `A` alongside with the result of computation of type `B`, which comes *second*. 114 | */ 115 | second(): Kleisli { 116 | return identity(this.M)().both(this); 117 | } 118 | 119 | /** 120 | * Discard the results of `this` computation and return `c`. 121 | * @param c Value of type `C` to return 122 | */ 123 | constant(c: C): Kleisli { 124 | return this.andThen(liftK(this.M)(() => c)); 125 | } 126 | 127 | /** 128 | * Discard the results of `this` computation. 129 | */ 130 | toVoid(): Kleisli { 131 | return this.constant(void 0); 132 | } 133 | 134 | /** 135 | * Discard the results of `this` computation and propagate the original input. 136 | * Effectively just keep the effect of `this` computation. 137 | */ 138 | asEffect(): Kleisli { 139 | return this.first().andThen(snd(this.M)()); 140 | } 141 | } 142 | 143 | /** 144 | * A pure functional computation from `A` to `Kind`, which **never** throws in runtime. 145 | * 146 | * @see Kleisli 147 | * 148 | * @template A domain type 149 | * @template B codomain type 150 | */ 151 | class Pure extends Kleisli { 152 | readonly tag = 'Pure'; 153 | constructor(readonly M: Monad1, readonly _run: (a: A) => Kind) { super(); } 154 | 155 | run = (a: A): Kind => this._run(a); 156 | } 157 | 158 | /** 159 | * An impure effectful computation from `A` to `B`, which may throw an exception of type `E` 160 | * 161 | * @see Kleisli 162 | * 163 | * @template A domain type 164 | * @template B codomain type 165 | */ 166 | class Impure extends Kleisli { 167 | readonly tag = 'Impure'; 168 | constructor(readonly M: Monad1, readonly _run: (a: A) => B) { super(); } 169 | 170 | run = (a: A): Kind => { 171 | const b = this._run(a); 172 | return this.M.of(b); 173 | } 174 | } 175 | 176 | /** 177 | * A right-to-left composition of two Kleisli functions. 178 | * 179 | * @see Kleisli 180 | * 181 | * @template A domain type 182 | * @template B codomain type 183 | */ 184 | class Compose extends Kleisli { 185 | readonly tag = 'Compose'; 186 | constructor( 187 | readonly M: Monad1, 188 | readonly g: Kleisli, 189 | readonly f: Kleisli, 190 | ) { super(); } 191 | 192 | run = (a: A): Kind => this.M.chain(this.f.run(a), this.g.run); 193 | } 194 | 195 | function isImpure(a: Kleisli): a is Impure { 196 | return a.tag === 'Impure'; 197 | } 198 | 199 | /** 200 | * Create a new instance of `Pure` computation. 201 | * @param f Function to run 202 | */ 203 | export function pure(M: Monad1) { 204 | return (f: (a: A) => Kind): Kleisli => new Pure(M, f); 205 | } 206 | 207 | /** 208 | * Create a new instance of `Impure` computation. 209 | * @param catcher Function to transform the error from `Error` into `E` 210 | * @param f Impure computation from `A` to `B` which may throw 211 | */ 212 | export function impure(M: Monad1) { 213 | return (catcher: (e: Error) => E) => (f: (a: A) => B): Kleisli => new Impure( 214 | M, 215 | (a: A) => { 216 | try { 217 | return f(a); 218 | } catch (error) { 219 | if (catcher(error) !== undefined) { 220 | throw new KleisliError(catcher(error)); 221 | } 222 | throw error; 223 | } 224 | }, 225 | ); 226 | } 227 | 228 | const voidCatcher = (e: Error): never => { throw e; }; 229 | 230 | /** 231 | * Create a new `Kleisli` computation from impure function which *you know* to never throw exceptions, 232 | * or throw exceptions which should lead to termination fo the program. 233 | * @param f Impure computation from `A` to `B` 234 | */ 235 | export function impureVoid(M: Monad1) { 236 | return (f: (a: A) => B): Kleisli => impure(M)(voidCatcher)(f); 237 | } 238 | 239 | /** 240 | * Lift the impure computation into `Kleisli` context. 241 | * @param f Impure function from `A` to `B` 242 | */ 243 | export function liftK(M: Monad1) { 244 | return (f: (a: A) => B): Kleisli => new Impure(M, f); 245 | } 246 | 247 | /** 248 | * Monadic `chain` function. 249 | * Apply function `f` to the result of current `Kleisli`, determining the next flow of computations. 250 | * @param fa Basic Kleisli computation 251 | * @param f Function from `B` to `Kleisli`, which represents next sequential computation 252 | */ 253 | export function chain(M: Monad1) { 254 | return (fa: Kleisli, f: (b: B) => Kleisli): Kleisli => 255 | pure(M)((a) => M.chain(fa.run(a), (b) => f(b).run(a))); 256 | } 257 | 258 | /** 259 | * Create a new `Kleisli` computation which result in `b`. 260 | * @param b Lazy value of type `B` 261 | */ 262 | export function point(M: Monad1) { 263 | return (b: () => B): Kleisli => liftK(M)(b); 264 | } 265 | 266 | /** 267 | * Applicative `of` function. 268 | * Lift a value of type `B` into a context of `Kleisli`. 269 | * @param b Value of type `B` 270 | */ 271 | export function of(M: Monad1) { 272 | return (b: B): Kleisli => liftK(M)(() => b); 273 | } 274 | 275 | /** 276 | * Tuple swap, lifted in `Kleisli` context. 277 | */ 278 | export function swap(M: Monad1) { 279 | return (): Kleisli => liftK(M)(([a, b]) => [b, a]); 280 | } 281 | 282 | /** 283 | * Perform right-to-left Kleisli arrows compotions. 284 | * @param second Second computation to apply 285 | * @param first First computation to apply 286 | */ 287 | export function composeK(M: Monad1) { 288 | return (second: Kleisli, first: Kleisli): Kleisli => 289 | isImpure(second) && isImpure(first) ? 290 | new Impure(M, flow(first._run, second._run)) : 291 | new Compose(M, second, first); 292 | } 293 | 294 | /** 295 | * Perform left-to-right Kleisli arrows compotions. 296 | * @param first First computation to apply 297 | * @param second Second computation to apply 298 | */ 299 | export function pipeK(M: Monad1) { 300 | return (first: Kleisli, second: Kleisli): Kleisli => 301 | composeK(M)(second, first); 302 | } 303 | 304 | /** 305 | * Depending on the input of type `Either`, execute either `l` or `r` branches. 306 | * @param l Left branch of computation 307 | * @param r Right branch of computation 308 | */ 309 | export function switchK(M: Monad1) { 310 | return (l: Kleisli, r: Kleisli): Kleisli, B> => 311 | isImpure(l) && isImpure(r) ? 312 | new Impure, B>(M, (a) => pipe( 313 | a, 314 | fold( 315 | (al) => l._run(al), 316 | (ar) => r._run(ar), 317 | )), 318 | ) : 319 | pure(M)((a) => pipe( 320 | a, 321 | fold( 322 | (al) => l.run(al), 323 | (ar) => r.run(ar), 324 | )), 325 | ); 326 | } 327 | 328 | /** 329 | * Execute `l` and `r` computations and if both succeed, process the results with `f`. 330 | * @param l First `Kleisli` computation 331 | * @param r Second `Kleisli` computation 332 | * @param f Function to process the results of both computations 333 | */ 334 | export function zipWith(M: Monad1) { 335 | return (l: Kleisli, r: Kleisli) => 336 | (f: (t: [B, C]) => D): Kleisli => 337 | isImpure(l) && isImpure(r) ? 338 | new Impure(M, (a) => f([l._run(a), r._run(a)])) : 339 | pure(M)((a) => M.chain(l.run(a), (b) => M.map(r.run(a), (c) => f([b, c])))); 340 | } 341 | 342 | /** 343 | * Propagate the input unchanged. 344 | */ 345 | export function identity(M: Monad1) { 346 | return (): Kleisli => liftK(M)((x) => x); 347 | } 348 | 349 | /** 350 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 351 | * A flipped version of @see right. 352 | * @param k Computation from `A` to `B` 353 | */ 354 | export function left(M: Monad1) { 355 | return (k: Kleisli): Kleisli, Either> => 356 | isImpure(k) ? 357 | new Impure(M, (a) => pipe(a, fold( 358 | (l) => eitherLeft(k._run(l)), 359 | (r) => eitherRight(r), 360 | ))) : 361 | pure(M)((a) => pipe(a, fold( 362 | (l) => M.map(k.run(l), (x) => eitherLeft(x)), 363 | (r) => M.of(eitherRight(r)), 364 | ))); 365 | } 366 | 367 | /** 368 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 369 | * A flipped version of @see left. 370 | * @param k Computation from `A` to `B` 371 | */ 372 | export function right(M: Monad1) { 373 | return (k: Kleisli): Kleisli, Either> => 374 | isImpure(k) ? 375 | new Impure(M, (a) => pipe(a, fold( 376 | (l) => eitherLeft(l), 377 | (r) => eitherRight(k._run(r)), 378 | ))) : 379 | pure(M)((a) => pipe(a, fold( 380 | (l) => M.of(eitherLeft(l)), 381 | (r) => M.map(k.run(r), (x) => eitherRight(x)), 382 | ))); 383 | } 384 | 385 | /** 386 | * Depending on the condition, propagate the original input through the left or right part of `Either`. 387 | * @param cond Predicate for `A` 388 | */ 389 | export function test(M: Monad1) { 390 | return (cond: Kleisli): Kleisli> => 391 | cond.both(identity(M)()).andThen(liftK(M)(([c, a]) => c ? eitherLeft(a) : eitherRight(a))); 392 | } 393 | 394 | /** 395 | * Depending on the condition, execute either `then` or `else`. 396 | * @param cond Predicate for `A` 397 | * @param then Computation to run if `cond` is `true` 398 | * @param else_ Computation to run if `cond` is `false` 399 | */ 400 | export function ifThenElse(M: Monad1) { 401 | return (cond: Kleisli) => 402 | (then: Kleisli) => (else_: Kleisli): Kleisli => 403 | isImpure(cond) && isImpure(then) && isImpure(else_) ? 404 | new Impure(M, (a) => cond._run(a) ? then._run(a) : else_._run(a)) : 405 | test(M)(cond).andThen(switchK(M)(then, else_)); 406 | } 407 | 408 | /** 409 | * Simplified version of @see ifThenElse without the `else` part. 410 | * @param cond Predicate for `A` 411 | * @param then Computation to run if `cond` is `true` 412 | */ 413 | export function ifThen(M: Monad1) { 414 | return (cond: Kleisli) => 415 | (then: Kleisli): Kleisli => ifThenElse(M)(cond)(then)(identity(M)()); 416 | } 417 | 418 | /** 419 | * While-loop: run `body` until `cond` is `true`. 420 | * @param cond Predicate for `A` 421 | * @param body Computation to run continuously until `cond` is `false` 422 | */ 423 | export function whileDo(M: Monad1) { 424 | return (cond: Kleisli) => (body: Kleisli): Kleisli => { 425 | if (isImpure(cond) && isImpure(body)) { 426 | return new Impure( 427 | M, 428 | (a0) => { 429 | let a = a0; 430 | 431 | while (cond._run(a)) { 432 | a = body._run(a); 433 | } 434 | 435 | return a; 436 | }, 437 | ); 438 | } else { 439 | const loop = (): Kleisli => 440 | pure(M)((a) => M.chain(cond.run(a), (b) => b ? M.chain(body.run(a), loop().run) : M.of(a))); 441 | 442 | return loop(); 443 | } 444 | }; 445 | } 446 | 447 | /** 448 | * Lifted version of `fst` tuple function. 449 | */ 450 | export function fst(M: Monad1) { 451 | return (): Kleisli => liftK(M)(([a]) => a); 452 | } 453 | 454 | /** 455 | * Lifted version of `snd` tuple function. 456 | */ 457 | export function snd(M: Monad1) { 458 | return (): Kleisli => liftK(M)(([, b]) => b); 459 | } 460 | 461 | /** 462 | * Convenience method which retruns instances of Kleisli API for the given monad. 463 | * @param M Monad1 & Bifunctor instance 464 | */ 465 | export function getInstancesFor(M: Monad1) { 466 | return ({ 467 | /** 468 | * Applicative `of` function. 469 | * Lift a value of type `B` into a context of `Kleisli`. 470 | * @param b Value of type `B` 471 | */ 472 | of: of(M), 473 | /** 474 | * Create a new instance of `Pure` computation. 475 | * @param f Function to run 476 | */ 477 | pure: pure(M), 478 | /** 479 | * Create a new instance of `Impure` computation. 480 | * @param catcher Function to transform the error from `Error` into `E` 481 | * @param f Impure computation from `A` to `B` which may throw 482 | */ 483 | impure: impure(M), 484 | /** 485 | * Create a new `Kleisli` computation from impure function which *you know* to never throw exceptions, 486 | * or throw exceptions which should lead to termination fo the program. 487 | * @param f Impure computation from `A` to `B` 488 | */ 489 | impureVoid: impureVoid(M), 490 | /** 491 | * Lift the impure computation into `Kleisli` context. 492 | * @param f Impure function from `A` to `B` 493 | */ 494 | liftK: liftK(M), 495 | /** 496 | * Monadic `chain` function. 497 | * Apply function `f` to the result of current `Kleisli`, determining the next flow of computations. 498 | * @param fa Basic Kleisli computation 499 | * @param f Function from `B` to `Kleisli`, which represents next sequential computation 500 | */ 501 | chain: chain(M), 502 | /** 503 | * Create a new `Kleisli` computation which result in `b`. 504 | * @param b Lazy value of type `B` 505 | */ 506 | point: point(M), 507 | /** 508 | * Tuple swap, lifted in `Kleisli` context. 509 | */ 510 | swap: swap(M), 511 | /** 512 | * Perform right-to-left Kleisli arrows compotions. 513 | * @param second Second computation to apply 514 | * @param first First computation to apply 515 | */ 516 | composeK: composeK(M), 517 | /** 518 | * Perform left-to-right Kleisli arrows compotions. 519 | * @param first First computation to apply 520 | * @param second Second computation to apply 521 | */ 522 | pipeK: pipeK(M), 523 | /** 524 | * Depending on the input of type `Either`, execute either `l` or `r` branches. 525 | * @param l Left branch of computation 526 | * @param r Right branch of computation 527 | */ 528 | switchK: switchK(M), 529 | /** 530 | * Execute `l` and `r` computations and if both succeed, process the results with `f`. 531 | * @param l First `Kleisli` computation 532 | * @param r Second `Kleisli` computation 533 | * @param f Function to process the results of both computations 534 | */ 535 | zipWith: zipWith(M), 536 | /** 537 | * Propagate the input unchanged. 538 | */ 539 | identity: identity(M), 540 | /** 541 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 542 | * A flipped version of @see right. 543 | * @param k Computation from `A` to `B` 544 | */ 545 | left: left(M), 546 | /** 547 | * Execute either the `k` computation or propagate the value of type `C` through, depending on an input. 548 | * A flipped version of @see left. 549 | * @param k Computation from `A` to `B` 550 | */ 551 | right: right(M), 552 | /** 553 | * Depending on the condition, propagate the original input through the left or right part of `Either`. 554 | * @param cond Predicate for `A` 555 | */ 556 | test: test(M), 557 | /** 558 | * Depending on the condition, execute either `then` or `else`. 559 | * @param cond Predicate for `A` 560 | * @param then Computation to run if `cond` is `true` 561 | * @param else_ Computation to run if `cond` is `false` 562 | */ 563 | ifThenElse: ifThenElse(M), 564 | /** 565 | * Simplified version of @see ifThenElse without the `else` part. 566 | * @param cond Predicate for `A` 567 | * @param then Computation to run if `cond` is `true` 568 | */ 569 | ifThen: ifThen(M), 570 | /** 571 | * While-loop: run `body` until `cond` is `true`. 572 | * @param cond Predicate for `A` 573 | * @param body Computation to run continuously until `cond` is `false` 574 | */ 575 | whileDo: whileDo(M), 576 | /** 577 | * Lifted version of `fst` tuple function. 578 | */ 579 | fst: fst(M), 580 | /** 581 | * Lifted version of `snd` tuple function. 582 | */ 583 | snd: snd(M), 584 | }); 585 | } 586 | 587 | export default getInstancesFor; 588 | -------------------------------------------------------------------------------- /src/unsafe.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Yuriy Bogomolov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { fold } from 'fp-ts/lib/Either'; 18 | import { identity } from 'fp-ts/lib/function'; 19 | import { IOEither } from 'fp-ts/lib/IOEither'; 20 | import { pipe } from 'fp-ts/lib/pipeable'; 21 | import { TaskEither } from 'fp-ts/lib/TaskEither'; 22 | 23 | /** 24 | * Unfolds the `TaskEither` structure and throws `E` as an exception, or returns `A` as a result. 25 | * 26 | * @example 27 | * const k: KleisliIO = liftK(M)(() => { 28 | * if (Math.random() > 0.5) { 29 | * throw new Error('oops'); 30 | * } 31 | * return 'foo'; 32 | * }); 33 | * const log: KleisliIO = impureVoid((s) => console.log(s)); 34 | * 35 | * unsafeRunTE(k.andThen(log).run()); // 🤞🏻 hope it doesn't blow up and prints 'foo' 36 | * 37 | * @param ie `TaskEither` to run 38 | */ 39 | export const unsafeRunTE = async (ie: TaskEither): Promise => pipe( 40 | (await ie()), 41 | fold((e) => { throw e; }, identity), 42 | ); 43 | 44 | /** 45 | * Unfolds the `IOEither` structure and throws `E` as an exception, or returns `A` as a result. 46 | * 47 | * @example 48 | * const k: KleisliIO = liftK(M)(() => { 49 | * if (Math.random() > 0.5) { 50 | * throw new Error('oops'); 51 | * } 52 | * return 'foo'; 53 | * }); 54 | * const log: KleisliIO = impureVoid((s) => console.log(s)); 55 | * 56 | * unsafeRunIE(k.andThen(log).run()); // 🤞🏻 hope it doesn't blow up and prints 'foo' 57 | * 58 | * @param ie `IOEither` to run 59 | */ 60 | export const unsafeRunIE = (ie: IOEither): A => pipe( 61 | ie(), 62 | fold((e) => { throw e; }, identity), 63 | ); 64 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "esnext", 8 | "moduleResolution": "node", 9 | "strict": true, 10 | "allowJs": false, 11 | "strictNullChecks": true, 12 | "experimentalDecorators": true, 13 | "allowSyntheticDefaultImports": true, 14 | "esModuleInterop": true, 15 | "noImplicitAny": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "strictFunctionTypes": false, 19 | "forceConsistentCasingInFileNames": true, 20 | "suppressImplicitAnyIndexErrors": true, 21 | "declaration": true, 22 | "types": [ 23 | "node", 24 | "mocha", 25 | "expect" 26 | ], 27 | "lib": [ 28 | "esnext", 29 | "dom" 30 | ] 31 | }, 32 | "include": [ 33 | "./src/**/*.ts", 34 | "./examples/**/*.ts" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } -------------------------------------------------------------------------------- /tsconfig.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src" 5 | }, 6 | "include": [ 7 | "./src/**/*.ts" 8 | ], 9 | "exclude": [ 10 | "./src/**/*.test.ts", 11 | ] 12 | } -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------