{"id":18357141,"url":"https://github.com/funkia/io","last_synced_at":"2025-04-06T12:32:34.197Z","repository":{"id":21196011,"uuid":"91898003","full_name":"funkia/io","owner":"funkia","description":"A library that turns impure code into pure and testable code.","archived":false,"fork":false,"pushed_at":"2022-12-30T18:24:10.000Z","size":737,"stargazers_count":38,"open_issues_count":10,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-22T00:33:59.957Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/funkia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-20T15:25:40.000Z","updated_at":"2024-05-21T09:29:19.000Z","dependencies_parsed_at":"2023-01-11T21:07:01.316Z","dependency_job_id":null,"html_url":"https://github.com/funkia/io","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/funkia%2Fio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/funkia%2Fio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/funkia%2Fio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/funkia%2Fio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/funkia","download_url":"https://codeload.github.com/funkia/io/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247484530,"owners_count":20946388,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T22:12:57.833Z","updated_at":"2025-04-06T12:32:29.185Z","avatar_url":"https://github.com/funkia.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg align=\"right\" src=\"https://avatars0.githubusercontent.com/u/21360882?v=3\u0026s=200\"\u003e\n\n[![Gitter](https://img.shields.io/gitter/room/funkia/General.svg)](https://gitter.im/funkia/General)\n[![Build Status](https://travis-ci.org/funkia/io.svg?branch=master)](https://travis-ci.org/funkia/io)\n[![codecov](https://codecov.io/gh/funkia/io/branch/master/graph/badge.svg)](https://codecov.io/gh/funkia/io)\n[![Dependency Status](https://david-dm.org/funkia/io.svg)](https://david-dm.org/funkia/io)\n[![npm version](https://badge.fury.io/js/%40funkia%2Fio.svg)](https://badge.fury.io/js/%40funkia%2Fio)\n\n# IO\n\n## Introduction\n\n`IO` is a structure for expressing imperative computations in a pure\nway. In a nutshell it gives us the convenience of imperative\nprogramming while preserving some of the properties of a purely\nfunctional programming. Most notable code that uses IO can be tested\nin a purely declarative way without actually running side-effects.\n\n## Table of contents\n\n* [Installation](#installation)\n* [Tutorial](#tutorial)\n* [API](#api)\n* [Contributing](#contributing)\n\n## Features\n\n* Provides a declarative and pure way to express code with side-effects\n* Has a nice API for easily testing `IO` code without running side-effects\n* Ships with both CommonJS and ES2015 modules for tree-shaking\n* Is written in TypeScript so comes with full comprehensive type\n  definitions\n\n## Installation\n\nIO can be installed from npm. The package ships with both\nCommonJS modules and ES6 modules\n\n```\nnpm install @funkia/io\n```\n\n## Tutorial\n\n### Impure functions\n\nLet's say we have a function `fireMissiles` that takes a number `n`\nand then fires `n` missiles. If fewer than `n` missiles are available\nthen only that amount of missiles is fired. The function returns the\namount of missiles that was successfully fired.\n\n```typescript\nfunction fireMissiles(amount: number): number { ... }\n```\n\nCertainly that is a very easy way of firing missiles. But\nunfortunately it is also impure. This, among other things, will make\nit tricky to test code using `fireMissiles` without actually firing\nmissiles every time the tests are run.\n\n### `IO` turns impure functions into pure ones\n\nTo solve the issue `IO` provides a method called `withEffects`. It\nconverts `fireMissiles` from an imperative procedure, that actually\nfires missiles, to a pure function that merely returns a _description_\nabout how to fire missiles.\n\n```typescript\nconst fireMissilesIO = withEffects(fireMissiles);\n```\n\n`fireMissilesIO` has the type `(amount: number) =\u003e IO\u003cnumber\u003e`. Here\n`IO\u003cnumber\u003e` means an IO-action that does something and then produces\na value of type `number`. The crucial difference about\n`fireMissilesIO` is that it has no side-effects and that it always\nreturn an equivalent IO-action when given the same number. It is pure.\n\nAt first this might seem like nothing but a neat trick. But it\nactually allows us to construct imperative computations in a\nfunctional way. To work with IO-actions we can use the fact that `IO`\nis a functor, an applicative and a monad. Thus we can for instance use\nit with go-notation.\n\n```javascript\nconst fireMissilesAndNotify = fgo(function*(amount) {\n  const n = yield fireMissilesIO(amount);\n  yield sendMessage(`${n} missiles successfully fired`);\n  return n;\n});\n```\n\nHere `sendMessage` has the type `(msg: string) =\u003e IO\u003cvoid\u003e`. It takes\na string and returns an IO-action that sends the specified message.\n\nNotice that the above code _looks_ like imperative code. In a sense it\n_is_ imperative code. It's a functional way of writing imperative\ncode. Since `sendMessage` is pure it satisfies referential\ntransparency. Instead of this:\n\n```javascript\ngo(function*() {\n  yield sendMessage(\"foo\");\n  yield sendMessage(\"foo\");\n});\n```\n\nWe can write this:\n\n```javascript\ngo(function*() {\n  const sendFoo = sendMessage(\"foo\");\n  yield sendFoo;\n  yield sendFoo;\n});\n```\n\nIf `sendMessage` had been impure this refactoring would not have\nworked–the side-effect in `sendMessage` would only have been carried\nout once. But since it's pure it's totally fine. In the dumb example\nabove it only made a small difference but in a real program being able\nto perform such refactorings can be very beneficial.\n\n### Asynchronous operations\n\nIO-actions can be asynchronous. This makes it possible to express\nasynchronous operations very conveniently. Instead of `withEffects` we\ncan use `withEffectsP` to turn an impure function that returns a\npromise into a pure function.\n\n```javascript\nconst fetchIO = withEffectsP(fetch);\n```\n\nThis creates a function with the return value `IO\u003cResponse\u003e`. If the\npromise returned by the wrapped function rejects the IO-computation\nwill result in an error. Error handling is described in the next\nsection.\n\n### Error handling\n\nThe `IO` monad comes with error handling features. It works through\nthe functions `throwE` and `catchE`. They resemble `throw` and `catch`\nbut instead of being language-features they are built into the `IO`\nimplementation.\n\nA value of `IO\u003cA\u003e` can not only produce a value of type `A`. It may\nalso produce an error.\n\nTo throw an error inside you use `throwE`:\n\n```javascript\nconst sendFriendlyMessageTo = fgo(function*(name, message) {\n  if (message.indexOf(\":)\") === -1) {\n    yield throwE(\"Please include a friendly smiley :)\");\n  }\n  const exists = yield checkUserExistence(name);\n  if (!exists) {\n    yield throwE(\"User does not exist\");\n  }\n  return yield sendMessageTo(name, message);\n});\n```\n\nOnce an error is `yield`ed the rest of the computation isn't being\nrun. The resulting `IO` value will produce an error instead of a\nvalue.\n\nTo catch an error you use `catchE`. As its first argument it takes a\nerror function handling. As its second argument it takes an `IO`\ncomputation. It returns a new `IO` computation.\n\n```javascript\nconst sendFriendlyMessageWithUnfriendlyError(name, message) {\n  return catchE(\n    (error) =\u003e \"Some error happened. I won't tell you which!\",\n    sendFriendlyMessageTo(name, message)\n  );\n}\n```\n\nHere is an example of using `fetchIO` with error handling. Since\nparsing the body from a `fetch` response as JSON is an asynchronous\noperation we define an additional function `responseJson`.\n\n```javascript\nconst responseJson = withEffectsP((response) =\u003e response.json());\n\nconst fetchUsersPet = fgo(function*(userId) {\n  const response = yield catchE(\n    (err) =\u003e throwE(`Request failed: ${err}`),\n    fetchIO(usersUrl + \"/\" + userId)\n  );\n  if (response.states === 404) {\n    yield throwE(\"User does not exist\");\n  }\n  const body: User = yield responseJson(response);\n  if (body.pet === undefined) {\n    yield throwE(\"User has no pet\");\n  } else {\n    return body.pet;\n  }\n});\n```\n\n### Running and testing\n\nAn IO-action can be run with the function `runIO`. The function\nactually performs the operations in the IO-action and returns a\npromise that resolves when it is done or rejects is the `IO` produces\nand unhandled error. `runIO` is an impure function.\n\nBesides running IO-actions we can also test them. Or \"dry-run\" them.\nTo see how this works consider one of the previous examples with a\nsmall bug added in:\n\n```javascript\nconst fireMissilesAndNotify = fgo(function*(amount) {\n  const n = yield fireMissilesIO(amount);\n  yield sendMessage(`${amount} missiles successfully fired`);\n  return n;\n});\n```\n\nThe error is that we don't send a message about how many missiles\nwhere actually fired. Instead we send the number of missiles that\nwhere requested to be fired. We can test the function with `testIO`:\n\n```javascript\nit(\"fires missiles and sends message\", () =\u003e {\n  testIO(fireMissilesAndNotify(10), [\n    [fireMissilesIO(10), 10],\n    [sendMessage(`10 missiles successfully fired`), undefined]\n  ], 10);\n});\n```\n\nThe first argument to `testIO` is the IO-action to test. The second is\na list of pairs. The first element in each pair is an IO-action that\nthe code should attempt to perform, the second element is the value\nthat performing the action should return. The last argument is the\nexpected result of the entire computation.\n\nHowever, the test above doesn't uncover the bug. Let's write another\none that does:\n\n```javascript\nit(\"fires missiles and sends message\", () =\u003e {\n  testIO(fireMissilesAndNotify(10), [\n    [fireMissilesIO(10), 5],\n    [sendMessage(`5 missiles successfully fired`), undefined]\n  ], 5);\n});\n```\n\nHere we specify that when the code attempts to run `fireMissilesIO(10)`\nit should get back the response `5`. After this the next line will\nthrow because our implementation passes a string to `sendMessage` that\nmentions `10` instead of `5`. Therefore `testIO` will throw and our\ntest will fail.\n\n## API\n\n### `IO.of(a: A): IO\u003cA\u003e`\n\nConverts any value into a IO that will return that value.\n\n### `withEffects((...args) =\u003e A): IO\u003cA\u003e`\n\nConverts an impure function into an `IO`\n\n### `withEffectsP(p: Promise\u003cA\u003e): IO\u003cA\u003e`\n\nConverts a Promise into an `IO`\n\n### `throwE(error: any): IO\u003cany\u003e`\n\nOnce an error is `yield`ed the rest of the computation isn't being\nrun. The resulting `IO` value will produce an error instead of a\nvalue.\n\n### `catchE(errorHandler: (error: any) =\u003e IO\u003cany\u003e, io: IO\u003cany\u003e): IO\u003cany\u003e`\n\nAs its first argument it takes a\nerror function handling. As its second argument it takes an `IO`\ncomputation. It returns a new `IO` computation.\n\n### `testIO\u003cA\u003e(e: IO\u003cA\u003e, arr: any[], a: A): void`\n\nThe first argument to `testIO` is the IO-action to test. The second is\na list of pairs. The first element in each pair is an IO-action that\nthe code should attempt to perform, the second element is the value\nthat performing the action should return. The last argument is the\nexpected result of the entire computation.\n\n## Contributing\n\nContributions are very welcome. Development happens as follows:\n\nInstall dependencies.\n```\nnpm install\n```\n\nRun tests.\n```\nnpm test\n```\nRunning the tests will generate an HTML coverage report in `./coverage/`.\n\nContinuously run the tests with\n```\nnpm run test-watch\n```\n\nWe also use `tslint` for ensuring a coherent code-style.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunkia%2Fio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffunkia%2Fio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunkia%2Fio/lists"}