{"id":13401049,"url":"https://github.com/r-lib/coro","last_synced_at":"2025-04-08T01:34:32.473Z","repository":{"id":41174361,"uuid":"104993652","full_name":"r-lib/coro","owner":"r-lib","description":"Coroutines for R","archived":false,"fork":false,"pushed_at":"2025-02-24T12:50:38.000Z","size":6769,"stargazers_count":168,"open_issues_count":13,"forks_count":11,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-04T20:57:50.156Z","etag":null,"topics":["async","coroutines","generator","iterator","promises","r","reticulate"],"latest_commit_sha":null,"homepage":"https://coro.r-lib.org/","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/r-lib.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-09-27T08:37:42.000Z","updated_at":"2025-04-04T18:41:35.000Z","dependencies_parsed_at":"2024-03-11T11:26:57.326Z","dependency_job_id":"699b46c1-c8ed-4671-8a06-43f499ccb566","html_url":"https://github.com/r-lib/coro","commit_stats":{"total_commits":509,"total_committers":7,"mean_commits":72.71428571428571,"dds":"0.056974459724950854","last_synced_commit":"5862d1a51f352e069b562b2d5e4267b3735d1943"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-lib%2Fcoro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-lib%2Fcoro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-lib%2Fcoro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-lib%2Fcoro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/r-lib","download_url":"https://codeload.github.com/r-lib/coro/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247760847,"owners_count":20991531,"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":["async","coroutines","generator","iterator","promises","r","reticulate"],"created_at":"2024-07-30T19:00:58.184Z","updated_at":"2025-04-08T01:34:32.450Z","avatar_url":"https://github.com/r-lib.png","language":"R","funding_links":[],"categories":["R"],"sub_categories":[],"readme":"---\noutput: github_document\nalways_allow_html: yes\n---\n\n\u003c!-- README.md is generated from README.Rmd. Please edit that file --\u003e\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#\u003e\",\n  fig.path = \"man/figures/README\",\n  out.width = \"100%\"\n)\n```\n\n\n# coro\n\n\u003c!-- badges: start --\u003e\n[![CRAN status](https://www.r-pkg.org/badges/version/coro)](https://cran.r-project.org/package=coro)\n[![R-CMD-check](https://github.com/r-lib/coro/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/coro/actions/workflows/R-CMD-check.yaml)\n\u003c!-- badges: end --\u003e\n\n\n## Overview\n\ncoro implements __coroutines__ for R, i.e. functions that can be suspended and resumed later on. There are two kinds:\n\n- __Async__ functions, which make it straightforward to program concurrently\n- __Generators__ for iterating over complex sequences\n\nSupported features:\n\n- Suspending within loops and if/else branches\n- Suspending within `tryCatch()`\n- `on.exit()` expressions and stack-based cleanup such as provided by `local_` functions in the [withr](https://github.com/r-lib/withr/) package\n- Step-debugging and `browser()` within coroutines\n\nCompatibility with:\n\n- Python iterators from the [reticulate](https://rstudio.github.io/reticulate/) package\n- Async operations from the [promises](https://github.com/rstudio/promises/) package\n- Parallel computations from the [future](https://github.com/HenrikBengtsson/future) package\n\nAttach the package to follow the examples:\n\n```{r}\nlibrary(coro)\n```\n\n\n### Async/await functions\n\nConcurrent programming is made straightforward by async-await functions. Whenever you are waiting for a result that may take a while (downloading a file, computing a value in an external process), use `await()`. The argument to `await()` must return a promise from the [promises](https://github.com/rstudio/promises/) package.\n\nConcurrent code based on promises can quickly become hard to write and follow. In the following artificial example, we wait for a download to complete, then decide to launch a computation in an external process depending on a property of the downloaded data. We also handle some errors specifically.\n\n```{r}\nmy_async \u003c- function() {\n  async_download() %\u003e%\n    then(function(data) {\n      if (ncol(data) \u003e 10) {\n        then(future::future(fib(30)), function(fib) {\n          data / fib\n        })\n      } else {\n        data\n      }\n    }, onRejected = function(err) {\n      if (inherits(err, \"download_error\")) {\n        NULL\n      } else {\n        stop(err)\n      }\n    })\n}\n```\n\nRewriting this function with async/await greatly simplifies the code:\n\n```{r}\nmy_async \u003c- async(function() {\n  data \u003c- tryCatch(\n    await(async_download()),\n    download_error = function(err) NULL\n  )\n\n  if (is.null(data)) {\n    return(NULL)\n  }\n\n  if (ncol(data) \u003e 10) {\n    fib \u003c- await(future::future(fib(30)))\n    data \u003c- data /fib\n  }\n\n  data\n})\n```\n\n\n### Generators\n\nGenerators are based on a simple iteration protocol:\n\n- Iterators are functions.\n- They can be advanced by calling the function. The new value is returned.\n- An exhausted iterator returns the sentinel symbol `exhausted`.\n\nThe `generator()` function creates a generator factory which returns generator instances:\n\n```{r}\n# Create a generator factory\ngenerate_abc \u003c- generator(function() {\n  for (x in letters[1:3]) {\n    yield(x)\n  }\n})\n\n# Create a generator instance\nabc \u003c- generate_abc()\n```\n\nA generator instance is an iterator function which yields values:\n\n```{r}\nabc\n\nabc()\n```\n\nCollect all remaining values from an iterator with `collect()`:\n\n```{r}\ncollect(abc)\n```\n\nIterate over an iterator with `loop()`:\n\n```{r}\nloop(for (x in generate_abc()) {\n  print(toupper(x))\n})\n```\n\nSee `vignette(\"generator\")` for more information.\n\n\n### Compatibility with the reticulate package\n\nPython iterators imported with the [reticulate](https://rstudio.github.io/reticulate/) package are compatible with `loop()` and `collect()`:\n\n```{r}\nsuppressMessages(library(reticulate))\n\npy_run_string(\"\ndef first_n(n):\n    num = 1\n    while num \u003c= n:\n        yield num\n        num += 1\n\")\n\nloop(for (x in py$first_n(3)) {\n  print(x * 2)\n})\n\n```\n\nThey can also be composed with coro generators:\n\n```{r}\ntimes \u003c- generator(function(it, n) for (x in it) yield(x * n))\n\ncomposed \u003c- times(py$first_n(3), 10)\n\ncollect(composed)\n```\n\n\n## Limitations\n\n`yield()` and `await()` can be used in loops, if/else branches, `tryCatch()` expressions, or any combinations of these. However they can't be used as function arguments. These will cause errors:\n\n```{r, eval = FALSE}\ngenerator(function() {\n  list(yield(\"foo\"))\n})\n\nasync(function() {\n  list(await(foo()))\n})\n```\n\nFortunately it is easy to rewrite the code to work around this limitation:\n\n```{r, eval = FALSE}\ngenerator(function() {\n  x \u003c- yield(\"foo\")\n  list(x)\n})\n\nasync(function() {\n  x \u003c- await(foo())\n  list(x)\n})\n```\n\n\n## How does it work\n\nCoroutines are an [abstraction for state machines](https://eli.thegreenplace.net/2009/08/29/co-routines-as-an-alternative-to-state-machines) in languages that support them. Conversely, you can implement coroutines by rewriting the code source provided by the user as a state machine. Pass `internals = TRUE` to the print methods of coroutines to reveal the state machine that is running under the hood:\n\n```{r}\nprint(generate_abc, internals = TRUE)\n```\n\nDespite this transformation of source code, `browser()` and step-debugging still work as you would expect. This is because coro keeps track of the source references from the original code.\n\n\n## Acknowledgements\n\n- The [regenerator](https://facebook.github.io/regenerator/) Javascript package which uses a similar transformation to implement generators and async functions in older versions of Javascript.\n\n- Gabor Csardi for many interesting discussions about concurrency and the design of coro.\n\n\n## Installation\n\nInstall the development version from github with:\n\n```r\n# install.packages(\"devtools\")\ndevtools::install_github(\"r-lib/coro\", build_vignettes = TRUE)\n```\n\n\n## Code of Conduct\n\nPlease note that the coro project is released with a [Contributor Code of Conduct](https://coro.r-lib.org/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fr-lib%2Fcoro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fr-lib%2Fcoro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fr-lib%2Fcoro/lists"}