├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── change-request.md │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .tidyrc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bower.json ├── packages.dhall ├── spago.dhall ├── src └── Control │ └── Coroutine │ └── Aff.purs └── test └── Main.purs /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report an issue 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of the bug. 11 | 12 | **To Reproduce** 13 | A minimal code example (preferably a runnable example on [Try PureScript](https://try.purescript.org)!) or steps to reproduce the issue. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Additional context** 19 | Add any other context about the problem here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/change-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Change request 3 | about: Propose an improvement to this library 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your change request related to a problem? Please describe.** 10 | A clear and concise description of the problem. 11 | 12 | Examples: 13 | 14 | - It's frustrating to have to [...] 15 | - I was looking for a function to [...] 16 | 17 | **Describe the solution you'd like** 18 | A clear and concise description of what a good solution to you looks like, including any solutions you've already considered. 19 | 20 | **Additional context** 21 | Add any other context about the change request here. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: PureScript Discourse 4 | url: https://discourse.purescript.org/ 5 | about: Ask and answer questions on the PureScript discussion forum. 6 | - name: PureScript Discord 7 | url: https://purescript.org/chat 8 | about: Ask and answer questions on the PureScript chat. 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description of the change** 2 | Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR. 3 | 4 | --- 5 | 6 | **Checklist:** 7 | 8 | - [ ] Added the change to the changelog's "Unreleased" section with a link to this PR and your username 9 | - [ ] Linked any existing issues or proposals that this pull request should close 10 | - [ ] Updated or added relevant documentation in the README and/or documentation directory 11 | - [ ] Added a test for the contribution (if applicable) 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up a PureScript toolchain 17 | uses: purescript-contrib/setup-purescript@main 18 | with: 19 | purescript: "unstable" 20 | purs-tidy: "latest" 21 | 22 | - name: Cache PureScript dependencies 23 | uses: actions/cache@v2 24 | with: 25 | key: ${{ runner.os }}-spago-${{ hashFiles('**/*.dhall') }} 26 | path: | 27 | .spago 28 | output 29 | 30 | - name: Install dependencies 31 | run: spago install 32 | 33 | - name: Build source 34 | run: spago build --no-install --purs-args '--censor-lib --strict' 35 | 36 | - name: Run tests 37 | run: spago test --no-install 38 | 39 | - name: Check formatting 40 | run: purs-tidy check src test 41 | 42 | - name: Verify Bower & Pulp 43 | run: | 44 | npm install bower pulp@16.0.0-0 45 | npx bower install 46 | npx pulp build -- --censor-lib --strict 47 | if [ -d "test" ]; then 48 | npx pulp test 49 | fi 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.github 4 | !.editorconfig 5 | !.tidyrc.json 6 | 7 | output 8 | generated-docs 9 | bower_components 10 | -------------------------------------------------------------------------------- /.tidyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "importSort": "source", 3 | "importWrap": "source", 4 | "indent": 2, 5 | "operatorsFile": null, 6 | "ribbon": 1, 7 | "typeArrowPlacement": "first", 8 | "unicode": "never", 9 | "width": null 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [Unreleased] 6 | 7 | Breaking changes: 8 | 9 | New features: 10 | 11 | Bugfixes: 12 | 13 | Other improvements: 14 | 15 | ## [v9.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v9.0.0) - 2022-04-28 16 | 17 | Breaking changes: 18 | - Update project and deps to PureScript v0.15.0 (#32 by @JordanMartinez) 19 | 20 | New features: 21 | 22 | Bugfixes: 23 | 24 | Other improvements: 25 | - Added `purs-tidy` formatter (#26 by @thomashoneyman) 26 | 27 | ## [v8.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v8.0.0) - 2021-02-26 28 | 29 | Breaking changes: 30 | - Added support for PureScript 0.14 and dropped support for all previous versions (#26) 31 | 32 | New features: 33 | 34 | Bugfixes: 35 | 36 | Other improvements: 37 | - Changed default branch to `main` from `master` 38 | - Updated to comply with Contributors library guidelines by adding new issue and pull request templates, updating documentation, and migrating to Spago for local development and CI 39 | 40 | ## [v7.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v7.0.0) - 2018-05-27 41 | 42 | - Updated for PureScript 0.12 43 | - The API has been revised in attempt to make it easier to understand 44 | 45 | ## [v6.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v6.0.0) - 2017-09-14 46 | 47 | - Updated for `aff-v4.0.0` (@natefaubion) 48 | 49 | ## [v5.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v5.0.0) - 2017-04-03 50 | 51 | - Updated for PureScript 0.11 52 | 53 | ## [v4.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v4.0.0) - 2016-10-23 54 | 55 | - Updated dependencies for PureScript 0.10.x 56 | 57 | ## [v3.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v3.0.0) - 2016-07-22 58 | 59 | - Updated for latest `purescript-coroutines` 60 | 61 | ## [v2.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v2.0.0) - 2016-06-09 62 | 63 | - Updated for `purescript-aff` 1.0. 64 | 65 | ## [v1.0.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v1.0.0) - 2016-06-07 66 | 67 | - Updated for 1.0 core libraries and 0.9.1 compiler. 68 | 69 | ## [v0.6.1](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.6.1) - 2016-05-02 70 | 71 | - Added license to bower.json (@hdgarrood) 72 | 73 | ## [v0.6.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.6.0) - 2016-03-11 74 | 75 | - Updated `purescript-aff` dependency 76 | 77 | ## [v0.5.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.5.0) - 2016-02-22 78 | 79 | - Updated to `aff-0.14.1` (@garyb) 80 | 81 | ## [v0.4.2](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.4.2) - 2016-01-12 82 | 83 | - Added `produce'` (@garyb) 84 | 85 | ## [v0.4.1](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.4.1) - 2015-11-20 86 | 87 | - Removed unused imports (@garyb) 88 | 89 | ## [v0.4.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.4.0) - 2015-09-23 90 | 91 | - Bumped dependencies. 92 | 93 | ## [v0.3.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.3.0) - 2015-08-26 94 | 95 | - Bumped dependencies. 96 | 97 | ## [v0.2.1](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.2.1) - 2015-08-14 98 | 99 | - Updated for Pursuit. 100 | 101 | ## [v0.2.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.2.0) - 2015-08-07 102 | 103 | - Bumped dependencies. 104 | 105 | ## [v0.1.1](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.1.1) - 2015-08-06 106 | 107 | - Updated dependency bounds 108 | 109 | ## [v0.1.0](https://github.com/purescript-contrib/purescript-aff-coroutines/releases/tag/v0.1.0) - 2015-08-02 110 | 111 | - Initial versioned release. 112 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Aff Coroutines 2 | 3 | Thanks for your interest in contributing to `aff-coroutines`! We welcome new contributions regardless of your level of experience or familiarity with PureScript. 4 | 5 | Every library in the Contributors organization shares a simple handbook that helps new contributors get started. With that in mind, please [read the short contributing guide on purescript-contrib/governance](https://github.com/purescript-contrib/governance/blob/main/contributing.md) before contributing to this library. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Phil Freeman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aff Coroutines 2 | 3 | [![CI](https://github.com/purescript-contrib/purescript-aff-coroutines/workflows/CI/badge.svg?branch=main)](https://github.com/purescript-contrib/purescript-aff-coroutines/actions?query=workflow%3ACI+branch%3Amain) 4 | [![Release](https://img.shields.io/github/release/purescript-contrib/purescript-aff-coroutines.svg)](https://github.com/purescript-contrib/purescript-aff-coroutines/releases) 5 | [![Pursuit](https://pursuit.purescript.org/packages/purescript-aff-coroutines/badge)](https://pursuit.purescript.org/packages/purescript-aff-coroutines) 6 | [![Maintainer: garyb](https://img.shields.io/badge/maintainer-garyb-teal.svg)](https://github.com/garyb) 7 | 8 | Helper functions for creating [coroutines](https://github.com/purescript-contrib/purescript-coroutines) with the Aff monad. 9 | 10 | ## Installation 11 | 12 | Install `aff-coroutines` with [Spago](https://github.com/purescript/spago): 13 | 14 | ```sh 15 | spago install aff-coroutines 16 | ``` 17 | 18 | ## Quick start 19 | 20 | The quick start hasn't been written yet (contributions are welcome!). The quick start covers a common, minimal use case for the library, whereas longer examples and tutorials are kept in the [docs directory](./docs). 21 | 22 | ## Documentation 23 | 24 | `aff-coroutines` documentation is stored in a few places: 25 | 26 | 1. Module documentation is [published on Pursuit](https://pursuit.purescript.org/packages/purescript-aff-coroutines). 27 | 2. Usage examples can be found in [the test suite](./test). 28 | 29 | If you get stuck, there are several ways to get help: 30 | 31 | - [Open an issue](https://github.com/purescript-contrib/purescript-aff-coroutines/issues) if you have encountered a bug or problem. 32 | - Ask general questions on the [PureScript Discourse](https://discourse.purescript.org) forum or the [PureScript Discord](https://purescript.org/chat) chat. 33 | 34 | ## Contributing 35 | 36 | You can contribute to `aff-coroutines` in several ways: 37 | 38 | 1. If you encounter a problem or have a question, please [open an issue](https://github.com/purescript-contrib/purescript-aff-coroutines/issues). We'll do our best to work with you to resolve or answer it. 39 | 40 | 2. If you would like to contribute code, tests, or documentation, please [read the contributor guide](./CONTRIBUTING.md). It's a short, helpful introduction to contributing to this library, including development instructions. 41 | 42 | 3. If you have written a library, tutorial, guide, or other resource based on this package, please share it on the [PureScript Discourse](https://discourse.purescript.org)! Writing libraries and learning resources are a great way to help this library succeed. 43 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-aff-coroutines", 3 | "license": "MIT", 4 | "ignore": [ 5 | "**/.*", 6 | "bower_components", 7 | "node_modules", 8 | "output", 9 | "bower.json", 10 | "package.json" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/purescript-contrib/purescript-aff-coroutines.git" 15 | }, 16 | "dependencies": { 17 | "purescript-aff": "^7.0.0", 18 | "purescript-avar": "^5.0.0", 19 | "purescript-coroutines": "^7.0.0", 20 | "purescript-effect": "^4.0.0" 21 | }, 22 | "devDependencies": { 23 | "purescript-console": "^6.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages.dhall: -------------------------------------------------------------------------------- 1 | let upstream = 2 | https://raw.githubusercontent.com/purescript/package-sets/prepare-0.15/src/packages.dhall 3 | 4 | in upstream 5 | -------------------------------------------------------------------------------- /spago.dhall: -------------------------------------------------------------------------------- 1 | { name = "aff-coroutines" 2 | , dependencies = 3 | [ "aff" 4 | , "avar" 5 | , "console" 6 | , "coroutines" 7 | , "effect" 8 | , "either" 9 | , "freet" 10 | , "maybe" 11 | , "newtype" 12 | , "prelude" 13 | , "transformers" 14 | ] 15 | , packages = ./packages.dhall 16 | , sources = [ "src/**/*.purs", "test/**/*.purs" ] 17 | } 18 | -------------------------------------------------------------------------------- /src/Control/Coroutine/Aff.purs: -------------------------------------------------------------------------------- 1 | -- | This module defines functions for creating coroutines on top of the `Aff` 2 | -- | monad. 3 | -- | 4 | -- | The `Aff` monad only supports actions which return a single value, 5 | -- | asynchronously, so this module provides a principled way to deal with 6 | -- | asynchronous _streams_ of values, and asynchronous consumers of streamed 7 | -- | data. 8 | module Control.Coroutine.Aff where 9 | 10 | import Prelude 11 | 12 | import Control.Coroutine (Producer, producer) 13 | import Effect.Aff (Aff, runAff, forkAff) 14 | import Effect.Aff.AVar as AVar 15 | import Effect.Aff.Class (class MonadAff, liftAff) 16 | import Effect (Effect) 17 | import Effect.Class (liftEffect) 18 | import Control.Monad.Free.Trans (hoistFreeT) 19 | import Control.Monad.Trans.Class (lift) 20 | import Data.Either (Either(..)) 21 | import Data.Newtype (class Newtype) 22 | 23 | newtype Emitter m a r = Emitter (Step a r -> m Unit) 24 | 25 | derive instance newtypeEmitter :: Newtype (Emitter m a r) _ 26 | 27 | data Step a b 28 | = Emit a 29 | | Finish b 30 | 31 | emit :: forall m a r. Emitter m a r -> a -> m Unit 32 | emit (Emitter f) = f <<< Emit 33 | 34 | close :: forall m a r. Emitter m a r -> r -> m Unit 35 | close (Emitter f) = f <<< Finish 36 | 37 | -- | Create a `Producer` using an asynchronous callback. 38 | -- | 39 | -- | The callback should provide zero or more values of type `a`, which will be 40 | -- | emitted by the `Producer`, terminated by an optional value of type `r`. No values 41 | -- | should be provided after a value of type `r` has been provided. 42 | -- | 43 | -- | For example: 44 | -- | 45 | -- | ```purescript 46 | -- | produce \emitter -> do 47 | -- | log "Working..." 48 | -- | emit emitter "progress" 49 | -- | log "Done!" 50 | -- | close emitter "finished" 51 | -- | ``` 52 | produce :: forall a r. (Emitter Effect a r -> Effect Unit) -> Producer a Aff r 53 | produce recv = produceAff \(Emitter send) -> 54 | liftEffect (recv (Emitter (void <<< runAff (const (pure unit)) <<< send))) 55 | 56 | -- | A version of `produce` that creates a `Producer` with an underlying 57 | -- | `MonadAff`, rather than `Aff` specifically. 58 | produce' :: forall a r m. MonadAff m => (Emitter Effect a r -> Effect Unit) -> Producer a m r 59 | produce' = hoistFreeT liftAff <<< produce 60 | 61 | -- | A variant of `produce` where the setup and callback functions use the `Aff` 62 | -- | monad. This can be helpful in certain cases. 63 | -- | 64 | -- | For example: 65 | -- | 66 | -- | ```purescript 67 | -- | produceAff \emitter -> do 68 | -- | delay $ Milliseconds 1000 69 | -- | emit emitter "progress" 70 | -- | delay $ Milliseconds 1000 71 | -- | close emitter "finished" 72 | -- | ``` 73 | produceAff :: forall a r. (Emitter Aff a r -> Aff Unit) -> Producer a Aff r 74 | produceAff recv = do 75 | v <- lift AVar.empty 76 | _ <- lift (forkAff (recv (Emitter (flip AVar.put v)))) 77 | producer $ AVar.take v <#> case _ of 78 | Emit a -> Left a 79 | Finish b -> Right b 80 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Control.Coroutine (Consumer, Producer, runProcess, consumer, ($$)) 6 | import Control.Coroutine.Aff (emit, close, produceAff) 7 | import Effect.Aff (Aff, Milliseconds(..), delay, runAff) 8 | import Effect (Effect) 9 | import Effect.Class (liftEffect) 10 | import Effect.Console (log, logShow) 11 | import Data.Either (either) 12 | import Data.Maybe (Maybe(..)) 13 | 14 | p :: Producer String Aff String 15 | p = produceAff \emitter -> do 16 | delay (Milliseconds 1000.0) 17 | emit emitter "Working..." 18 | delay (Milliseconds 1000.0) 19 | emit emitter "Working..." 20 | delay (Milliseconds 1000.0) 21 | emit emitter "Working..." 22 | delay (Milliseconds 1000.0) 23 | close emitter "Done!" 24 | 25 | c :: Consumer String Aff String 26 | c = consumer \s -> liftEffect (log s) $> Nothing 27 | 28 | main :: Effect Unit 29 | main = void $ runAff (either logShow log) $ runProcess (p $$ c) 30 | --------------------------------------------------------------------------------