From 2b9f60817ba958dafb7104e3a56454471ab08324 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Fri, 9 Oct 2020 15:17:51 -0700 Subject: [PATCH] Update for Contributors library guidelines (#195) --- .editorconfig | 13 ++ .eslintrc.json | 29 +++ .github/ISSUE_TEMPLATE/bug-report.md | 19 ++ .github/ISSUE_TEMPLATE/change-request.md | 21 ++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/PULL_REQUEST_TEMPLATE.md | 11 + .github/workflows/ci.yml | 51 +++++ .gitignore | 22 +- .jscsrc | 17 -- .jshintrc | 19 -- .travis.yml | 23 -- CHANGELOG.md | 249 +++++++++++++++++++++ CONTRIBUTING.md | 5 + README.md | 261 +++-------------------- bower.json | 2 +- docs/README.md | 201 +++++++++++++++++ package.json | 15 +- packages.dhall | 4 + spago.dhall | 19 ++ src/Effect/Aff.js | 2 +- 20 files changed, 670 insertions(+), 321 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintrc.json create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/change-request.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/ci.yml delete mode 100644 .jscsrc delete mode 100644 .jshintrc delete mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 docs/README.md create mode 100644 packages.dhall create mode 100644 spago.dhall diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7c68b07 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# https://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2f987eb --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,29 @@ +{ + "env": { "commonjs": true }, + "extends": "eslint:recommended", + "parserOptions": { "ecmaVersion": 5 }, + "rules": { + "block-scoped-var": "error", + "consistent-return": "error", + "eqeqeq": "error", + "guard-for-in": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-constant-condition": ["error", { "checkLoops": false }], + "no-extra-parens": "off", + "no-extend-native": "error", + "no-new": "error", + "no-param-reassign": "error", + "no-return-assign": "error", + "no-sequences": "error", + "no-unused-expressions": "error", + "no-use-before-define": "error", + "no-undef": "error", + "no-eq-null": "error", + "radix": ["error", "always"], + "indent": ["error", 2, { "SwitchCase": 0 }], + "quotes": ["error", "double"], + "semi": ["error", "always"], + "strict": ["error", "global"] + } +} diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..b79b995 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,19 @@ +--- +name: Bug report +about: Report an issue +title: "" +labels: bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of the bug. + +**To Reproduce** +A minimal code example (preferably a runnable example on [Try PureScript](https://try.purescript.org)!) or steps to reproduce the issue. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/change-request.md b/.github/ISSUE_TEMPLATE/change-request.md new file mode 100644 index 0000000..a2ee685 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/change-request.md @@ -0,0 +1,21 @@ +--- +name: Change request +about: Propose an improvement to this library +title: "" +labels: "" +assignees: "" +--- + +**Is your change request related to a problem? Please describe.** +A clear and concise description of the problem. + +Examples: + +- It's frustrating to have to [...] +- I was looking for a function to [...] + +**Describe the solution you'd like** +A clear and concise description of what a good solution to you looks like, including any solutions you've already considered. + +**Additional context** +Add any other context about the change request here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c47a263 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: PureScript Discourse + url: https://discourse.purescript.org/ + about: Ask and answer questions here. + - name: Functional Programming Slack + url: https://functionalprogramming.slack.com + about: For casual chat and questions (use https://fpchat-invite.herokuapp.com to join). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d8780f7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +**Description of the change** +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. + +--- + +**Checklist:** + +- [ ] Added the change to the changelog's "Unreleased" section with a link to this PR and your username +- [ ] Linked any existing issues or proposals that this pull request should close +- [ ] Updated or added relevant documentation in the README and/or documentation directory +- [ ] Added a test for the contribution (if applicable) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0b3ca67 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up PureScript toolchain + uses: purescript-contrib/setup-purescript@main + + - name: Cache PureScript dependencies + uses: actions/cache@v2 + with: + key: ${{ runner.os }}-spago-${{ hashFiles('**/*.dhall') }} + path: | + .spago + output + + - name: Set up Node toolchain + uses: actions/setup-node@v1 + with: + node-version: "12.x" + + - name: Cache NPM dependencies + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Install NPM dependencies + run: npm install + + - name: Build the project + run: npm run build + + - name: Run tests + run: npm run test diff --git a/.gitignore b/.gitignore index 89aa538..5a54e2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,13 @@ -/.* -!/.gitignore -!/.github -!/.travis.yml +.* +!.gitignore +!.github +!.editorconfig +!.eslintrc.json -# Dependencies -bower_components -node_modules - -# Generated files output -dce-output generated-docs +bower_components -# Lockfiles +node_modules package-lock.json *.lock - -# Extra files -!/.jshintrc -!/.jscsrc diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 2561ce9..0000000 --- a/.jscsrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "preset": "grunt", - "disallowSpacesInFunctionExpression": null, - "requireSpacesInFunctionExpression": { - "beforeOpeningRoundBrace": true, - "beforeOpeningCurlyBrace": true - }, - "disallowSpacesInAnonymousFunctionExpression": null, - "requireSpacesInAnonymousFunctionExpression": { - "beforeOpeningRoundBrace": true, - "beforeOpeningCurlyBrace": true - }, - "disallowSpacesInsideObjectBrackets": null, - "requireSpacesInsideObjectBrackets": "all", - "validateQuoteMarks": "\"", - "requireCurlyBraces": null -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 81e6de7..0000000 --- a/.jshintrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "bitwise": true, - "eqeqeq": true, - "forin": true, - "freeze": true, - "funcscope": true, - "futurehostile": true, - "strict": "global", - "latedef": true, - "noarg": true, - "nocomma": true, - "nonew": true, - "notypeof": true, - "singleGroups": true, - "undef": true, - "unused": true, - "eqnull": true, - "predef": ["exports"] -} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6da455e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: node_js -dist: trusty -sudo: required -node_js: stable -env: - - PATH=$HOME/purescript:$PATH -install: - - TAG=$(basename $(curl --location --silent --output /dev/null -w %{url_effective} https://github.com/purescript/purescript/releases/latest)) - - curl --location --output $HOME/purescript.tar.gz https://github.com/purescript/purescript/releases/download/$TAG/linux64.tar.gz - - tar -xvf $HOME/purescript.tar.gz -C $HOME/ - - chmod a+x $HOME/purescript - - npm install -g bower - - npm install - - bower install --production -script: - - npm run -s build - - bower install - - npm run -s test -after_success: - - >- - test $TRAVIS_TAG && - echo $GITHUB_TOKEN | pulp login && - echo y | pulp publish --no-push diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d5a3f11 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,249 @@ +# Changelog + +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). + +## [Unreleased] + +Breaking changes (😱!!!): + +New features: + +Bugfixes: + +Other improvements: + +## [v5.1.2](https://github.com/purescript-contrib/purescript-aff/releases/tag/v5.1.2) - 2019-09-11 + +- Try to recover when exceptions are thrown in pure code (@ford-prefect) +- Fix memory leak in supervisors where child fibers are retained even though they have completed (@eric-corumdigital) + +## [v5.1.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v5.1.1) - 2019-03-29 + +- Fixes supervision when no child fibers are active (#164) +- Fixes various bugs around resumption within a bracket mask (#171) + +## [v5.1.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v5.1.0) - 2018-12-14 + +- Adds `fiberCanceler` (@safareli) + +## [v5.0.2](https://github.com/purescript-contrib/purescript-aff/releases/tag/v5.0.2) - 2018-08-24 + +- Don't resume from an enqueued task if interrupted (#162) +- Fix finalization after failure (#161) + +## [v5.0.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v5.0.1) - 2018-07-12 + +- Fixes runtime error when running an async canceler in a `ParAff` `apply` operation (#153) + +## [v5.0.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v5.0.0) - 2018-05-25 + +Updated for PureScript 0.12 + +## [v4.1.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.1.1) - 2018-04-18 + +- Fixes `bhead is not a function` FFI errors when yielding a fork at the tail of a fresh attempt context (possibly through `bracket` acquisition or `catchError`). + +## [v4.1.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.1.0) - 2018-04-06 + +- Added `Lazy` instance for `Aff` (@safareli) + +## [v4.0.2](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.2) - 2018-01-14 + +- Fix regression in ParAff Applicative behavior when an exception occurs. + +## [v4.0.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.1) - 2017-11-19 + +- Fixes JavaScript runtime error in `ParAff` cancellation. + +## [v4.0.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.0) - 2017-09-14 + +This release (v4.0.0) features a revamped API for writing more expressive asynchronous programs with stronger guarantees. + +- `Fiber` cooperative multi-tasking primitive for fork/join workflows. +- Stronger cleanup guarantees with `bracket` and `supervise`. +- Reformulated `AVar` semantics +- Rewritten core with an emphasis on performance and consistency. + +**New Features and Enhancements** + +**`Fiber`** + +Previously, `Aff`s supported `forkAff`, but it was very difficult to get values back when forked computations completed. Libraries like `purescript-aff-future` were written to overcome this limitation (though with limitations of their own). The semantics of `purescript-aff-future` have been assimilated into `Aff` through the `Fiber` type without any of the previous limitaions (like lack of cancellation). `Fiber`s make it easy to not only fork computations, but also share their results among many consumers with `joinFiber`, which will wait until the `Fiber` completes, or yield immediately if it has already resolved. If a `Fiber` threw an exception, then the exception will be rethrown in the observer. `Fiber`s additionally support cancellation (`killFiber`) and finalizers for cleanup (`bracket`). + +**`bracket`** + +When we kill/cancel a `Fiber`, often times we need to make sure some resource gets released. `bracket` lets you take control of the acquire/use/release resource cycle. + +```purescript +example = + bracket + (openFile myFile) -- Acquire a resource + (\file -> closeFile file) -- Release the resource + (\file -> appendFile "hello" file) -- Use the resource +``` + +In the example above, the runtime will always ensure the "release" effect will run even in the presence of cancellation. There is also `generalBracket`, which lets you observe whether the primary action completed successfully, threw an exception, or was killed asynchronously and run different cleanup effects accordingly. + +**`supervise`** + +Sometimes we need to fork many `Fiber`s for a task, but it's possible (often through cancellation) for these sub-tasks to leak. We've introduced a `supervise` combinator which will automatically keep track of forked `Fiber`s and clean them up and run their finalizers once the computation completes or is cancelled. + +```purescript +example = supervise do + _ <- forkAff requestA + _ <- forkAff requestB + requestC +``` + +In the above example, if `requestA` or `requestB` are still running when `requestC` completes, they will be killed by the runtime and have their finalizers run. + +**`suspendAff`** + +As an alternative to `forkAff`, which eagerly forks and runs a computations, we've introduced `suspendAff`. This forks a computation but does not initiate it until a result is demanded via `joinFiber`. Results are still memoized (as all `Fiber` results are), but are just computed lazily. + +**Stack-safety** + +With the old callback approach, each bind resulted in more and more stack, and it was trivial to blow the stack unless you explicitly used `tailRecM`. The `Aff` interpreter now uses a constant amount of stack space, making `tailRecM` unnecesary. This extends to `ParAff` as well. + +**Uncaught exceptions** + +Previously, exceptions thrown in forked computations were completely swallowed. This made it extremely difficult to diagnose bugs. Now if a `Fiber` has no observers and it throws an exception, the exception will always be rethrown in a fresh stack. This can be observed by things like `window.onerror` or just by watching the console. + +**Breaking Changes** + +- The low-level callback representation is no longer relevant. If you've defined `Aff` effects via the FFI, you should transition to using `Control.Monad.Aff.Compat`, which provides an `EffFn` adapter. This makes it easy to use idiomatic JavaScript callbacks when building `Aff` actions. +- The `AVar` API methods have changed to match Haskell's `MVar` API. `putVar` now _blocks_ until the `AVar` actually assimilates the value. Previously, `putVar` would queue the value, but yield immediately. It's possible to recover similar behavior as the old API with `forkAff (try (putVar value avar))` (though this should be considered mildly unsafe), or you can use `tryPutVar` which will attempt a synchronous put. +- Argument order for `AVar` and `Fiber` operations consistently put the subject last. +- Several unlawful instances where removed for `Aff` (`Alternative`, `MonadPlus`, and `MonadZero`). +- `forkAff` now returns a `Fiber` rather than a `Canceler`. +- `forkAll` was removed. Just use `Traversable` and `forkAff` directly. +- `cancel` was removed. Use `killFiber`. +- The signature of `makeAff` has changed to provide a _single_ callback which takes an `Either Error a` argument. `Canceler`s are also _required_. If you are sure you have no means of cancelling an action, you can use `nonCanceler` or `mempty`. +- `Canceler`s no longer yield `Boolean`. This was meaningless and not useful, so all cancellation effects now yield `Unit`. +- `ParAff` is no longer a newtype. Parallel computations should be constructed via `Control.Parallel` with `parallel` and `sequential`. + +## [v4.0.0-rc.6](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.0-rc.6) - 2017-09-12 + +- Rename `atomically` to `invincible`. +- Killing a suspended fiber should be synchronous. + +## [v4.0.0-rc.5](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.0-rc.5) - 2017-08-31 + +- Changed the argument order of `AVar` operations to have the AVar last. + +## [v4.0.0-rc.4](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.0-rc.4) - 2017-08-26 + +- Reexport things in `Control.Monad.Eff.Exception` relevant to the `Aff` API. + +## [v4.0.0-rc.3](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.0-rc.3) - 2017-08-24 + +- `kill` always succeeds. If a finalizer throws, it will rethrow in a fresh stack. +- Fixes the behavior of `throwError` and `generalBracket` within a finalizer. + +## [v4.0.0-rc.2](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.0-rc.2) - 2017-08-20 + +- Fixes `ParAff` `Alt` behavior when propagating exceptions. + +## [v4.0.0-rc.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v4.0.0-rc.1) - 2017-08-19 + +This pre-release for version v4.0.0 features a revamped API for writing more expressive asynchronous programs with stronger guarantees. + +- `Fiber` cooperative multi-tasking primitive for fork/join workflows +- Stronger cleanup guarantees with `bracket` and `supervise`. +- Reformulated `AVar` semantics + +**Migration Notes** + +- The low-level callback representation is no longer relevant. If you've defined `Aff` effects via the FFI, you should transition to using `Control.Monad.Eff.Compat`, which provides an `EffFn` adapter. +- The `AVar` API methods have changed to match Haskell's `MVar` API. `putVar` effects now block until matched with a `takeVar`. It's possible to recover similar behavior as the old API with `forkAff (try (putVar avar value))`. +- Several unlawful instances where removed for `Aff` (`Alternative`, `MonadPlus`, and `MonadZero`). + +## [v3.1.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v3.1.0) - 2017-05-08 + +- Added `tryTakeVar` and `tryPeekVar` (@syaiful6) + +## [v3.0.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v3.0.0) - 2017-04-02 + +- Updated to work with PureScript 0.11 +- Removed `later` and `later'` in favor of `delay`. + +## [v2.0.3](https://github.com/purescript-contrib/purescript-aff/releases/tag/v2.0.3) - 2017-02-18 + +- Avoid `Discard` constraint arising in upcoming PureScript version (@mlang) + +## [v2.0.2](https://github.com/purescript-contrib/purescript-aff/releases/tag/v2.0.2) - 2016-12-07 + +- Fixed broken `peekVar` (@natefaubion) +- Fixed `Alt` instance for parallel to cancel the "losing" side (@garyb) + +## [v2.0.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v2.0.1) - 2016-10-23 + +- Fixed `Applicative` instance for `ParAff` #76 + +## [v2.0.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v2.0.0) - 2016-10-21 + +- Updated dependencies, now compatible with PureScript 0.10 +- Added `peekVar` that reads from an `AVar` without consuming + +## [v1.1.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v1.1.0) - 2016-07-24 + +- Added functions to `Control.Monad.Aff.Console` to match `Control.Monad.Eff.Console`, re-export `CONSOLE` effect (@texastoland) + +## [v1.0.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v1.0.0) - 2016-06-09 + +- Initial 1.0.0 release + +## [v0.17.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.17.0) - 2016-06-05 + +- Requires PureScript 0.9.1 or later. + +## [v0.16.2](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.16.2) - 2016-06-01 + +- The `MonadRec` instance now preserves synchronous semantics instead of periodically bouncing asynchronously. + +## [v0.16.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.16.0) - 2016-03-11 + +- Removed `Affable` class again +- `MonadAff` is now a subclass of `MonadEff` + +## [v0.15.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.15.0) - 2016-03-09 + +- Added `Affable` class +- `MonadAff` now is a subclass of `Affable` and `Monad` + +## [v0.14.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.14.1) - 2016-02-22 + +- Added `forkAll` combinator, for forking many asynchronous computations in a synchronous manner (@natefaubion) + +## [v0.13.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.13.1) - 2015-11-19 + +- Fixed warnings + +## [v0.13.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.13.0) - 2015-09-22 + +- Bump transformers dependency + +## [v0.11.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.11.0) - 2015-07-02 + +This release is intended for 0.7 of the PureScript compiler. + +## [v0.10.1](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.10.1) - 2015-04-18 + +This release fixes a number of bugs related to cancelation, and greatly improves the semantics of cancelation. Several tests have been added or made more sophisticated. + +## [v0.10.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.10.0) - 2015-04-14 + +This release adds a newtype for `Canceler` so that `Semigroup` and `Monoid` instances may be defined, allowing easy composition of multiple cancelers into a single canceler. + +In addition, `Par` has been rewritten to use a new generic, exported `cancelWith` combinator, so that canceling a parallel computation will now cancel the individual components of the computation. + +This is mostly a drop-in upgrade, but if you created your own cancelers before, you'll now need to use the newtype. + +## [v0.7.0](https://github.com/purescript-contrib/purescript-aff/releases/tag/v0.7.0) - 2015-03-24 + +This release includes the following enhancements: +- Native representation of `Aff` which minimizes the amount of wrapping that is performed. This should be much faster than the previous version. +- Automatically catch exceptions and propagate along the error channel, so that, for example, ill-typed `Eff` code won't halt an `Aff` application. +- Forked computations can be killed if the computation supports it. +- The effect type of asynchronous code has been simplified; in particular, the mere act of being asynchronous is not considered an effect. +- Documentation has been updated to account for the changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8f55bab --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# Contributing to Aff + +Thanks for your interest in contributing to `aff`! We welcome new contributions regardless of your level of experience or familiarity with PureScript. + +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. diff --git a/README.md b/README.md index fa08588..3cb47d7 100644 --- a/README.md +++ b/README.md @@ -1,258 +1,51 @@ -# purescript-aff +# Aff -[![Latest release](http://img.shields.io/github/release/slamdata/purescript-aff.svg)](https://github.com/slamdata/purescript-aff/releases) -[![Build status](https://travis-ci.org/slamdata/purescript-aff.svg?branch=master)](https://travis-ci.org/slamdata/purescript-aff) +[![CI](https://github.com/purescript-contrib/purescript-aff/workflows/CI/badge.svg?branch=main)](https://github.com/purescript-contrib/purescript-aff/actions?query=workflow%3ACI+branch%3Amain) +[![Release](https://img.shields.io/github/release/purescript-contrib/purescript-aff.svg)](https://github.com/purescript-contrib/purescript-aff/releases) +[![Pursuit](https://pursuit.purescript.org/packages/purescript-aff/badge)](https://pursuit.purescript.org/packages/purescript-aff) +[![Maintainer: natefaubion](https://img.shields.io/badge/maintainer-natefaubion-teal.svg)](https://github.com/natefaubion) +[![Maintainer: thomashoneyman](https://img.shields.io/badge/maintainer-thomashoneyman-teal.svg)](https://github.com/thomashoneyman) An asynchronous effect monad and threading model for PureScript. -# Example - -Note that these documentation examples are partially pseudocode and are unlikely to compile. Working examples can be found [here](https://github.com/purescript-contrib/purescript-aff/blob/master/test/Test/Main.purs). - -```purescript -main = launchAff do - response <- Ajax.get "http://foo.bar" - log response.body -``` - -# Getting Started - ## Installation -``` -bower install purescript-aff -``` - -## Introduction - -An example of `Aff` is shown below: - -```purescript -deleteBlankLines path = do - contents <- loadFile path - let contents' = S.join "\n" $ A.filter (\a -> S.length a > 0) (S.split "\n" contents) - saveFile path contents' -``` - -This looks like ordinary, synchronous, imperative code, but actually operates -asynchronously without any callbacks. Error handling is baked in so you only -deal with it when you want to. - -The library contains instances for `Semigroup`, `Monoid`, `Apply`, -`Applicative`, `Bind`, `Monad`, `Alt`, `Plus`, `MonadEffect`, `MonadError`, and -`Parallel`. These instances allow you to compose asynchronous code as easily -as `Effect`, as well as interop with existing `Effect` code. - -## Escaping Callback Hell - -Hopefully, you're using libraries that already use the `Aff` type, so you -don't even have to think about callbacks! - -If you're building your own library, then you can make an `Aff` from -low-level `Effect` callbacks with `makeAff`. +Install `aff` with [Spago](https://github.com/purescript/spago): -```purescript -makeAff :: forall a. ((Either Error a -> Effect Unit) -> Effect Canceler) -> Aff a -``` - -This function expects you to provide a handler, which should call the -supplied callback with the result of the asynchronous computation. - -You should also return `Canceler`, which is just a cleanup effect. Since -`Aff` threads may be killed, all asynchronous operations should provide a -mechanism for unscheduling it. - -`Effect.Aff.Compat` provides functions for easily binding FFI -definitions: - -```javascript -exports._ajaxGet = function (request) { // accepts a request - return function (onError, onSuccess) { // and callbacks - var req = doNativeRequest(request, function (err, response) { // make the request - if (err != null) { - onError(err); // invoke the error callback in case of an error - } else { - onSuccess(response); // invoke the success callback with the reponse - } - }); - - // Return a canceler, which is just another Aff effect. - return function (cancelError, cancelerError, cancelerSuccess) { - req.cancel(); // cancel the request - cancelerSuccess(); // invoke the success callback for the canceler - }; - }; -}; -``` - -```purescript -foreign import _ajaxGet :: Request -> EffectFnAff Response +```sh +spago install aff ``` -We can wrap this into an asynchronous computation like so: +## Quick start -```purescript -ajaxGet :: Request -> Aff Response -ajaxGet = fromEffectFnAff <<< _ajaxGet -``` - -This eliminates callback hell and allows us to write code simply using `do` -notation: +This quick start covers common, minimal use cases for the library. Longer examples and tutorials can be found in the [docs directory](./docs). ```purescript -example = do - response <- ajaxGet req +main :: Effect Unit +main = launchAff_ do + response <- Ajax.get "http://foo.bar" log response.body ``` -## Effect - -All purely synchronous computations (`Effect`) can be lifted to asynchronous -computations with `liftEffect` defined in `Effect.Class`. - -```purescript -liftEffect $ log "Hello world!" -``` - -This lets you write your whole program in `Aff`, and still call out to -synchronous code. +## Documentation -## Dealing with Failure +`aff` documentation is stored in a few places: -`Aff` has error handling baked in, so ordinarily you don't have to worry -about it. For control-flow exceptions, it's advised to use `ExceptT` -instead throwing errors in the `Aff` context. The support for errors -mainly exists to notify you when very bad things happen. +1. Module documentation is [published on Pursuit](https://pursuit.purescript.org/packages/purescript-aff). +2. Written documentation is kept in the [docs directory](./docs). +3. Usage examples can be found in [the test suite](./test). -However, when you need to deal with `Aff` errors, you have a few options. +If you get stuck, there are several ways to get help: - 1. **Alt** - 2. **MonadError** - 3. **Bracketing** +- [Open an issue](https://github.com/purescript-contrib/purescript-aff/issues) if you have encountered a bug or problem. +- [Search or start a thread on the PureScript Discourse](https://discourse.purescript.org) if you have general questions. You can also ask questions in the `#purescript` and `#purescript-beginners` channels on the [Functional Programming Slack](https://functionalprogramming.slack.com) ([invite link](https://fpchat-invite.herokuapp.com/)). -#### 1. Alt +## Contributing -Because `Aff` has an `Alt` instance, you may also use the operator `<|>` to -provide an alternative computation in the event of failure: +You can contribute to `aff` in several ways: -```purescript -example = do - result <- Ajax.get "http://foo.com" <|> Ajax.get "http://bar.com" - pure result -``` - -#### 2. MonadError - -`Aff` has a `MonadError` instance, which comes with three functions: -`try`, `catchError`, and `throwError`. - -These are defined in -[purescript-transformers](http://github.com/purescript/purescript-transformers). -Here's an example of how you can use them: - -```purescript -tryExample = do - result <- try $ Ajax.get "http://foo.com" - case result of - Left err -> pure "" - Right resp -> pure resp.body - -catchThrowExample = do - resp <- Ajax.get "http://foo.com" `catchError` \_ -> pure defaultResponse - when (resp.statusCode /= 200) do - throwError myErr - pure resp.body -``` - -#### 3. Bracketing - -`Aff` threads can be cancelled, but sometimes we need to guarantee an action -gets run even in the presence of exceptions or cancellation. Use `bracket` to -acquire resources and clean them up. - -```purescript -example = - bracket - (openFile myFile) - (\file -> closeFile file) - (\file -> appendFile "hello" file) -``` - -In this case, `closeFile` will always be called regardless of exceptions once -`openFile` completes. - -## Forking - -Using `forkAff`, you can "fork" an asynchronous computation, which means -that its activities will not block the current thread of execution: - -```purescript -forkAff myAff -``` - -Because Javascript is single-threaded, forking does not actually cause the -computation to be run in a separate thread. Forking just allows the subsequent -actions to execute without waiting for the forked computation to complete. - -Forking returns a `Fiber a`, representing the deferred computation. You can -kill a `Fiber` with `killFiber`, which will run any cancelers and cleanup, and -you can observe a `Fiber`'s final value with `joinFiber`. If a `Fiber` threw -an exception, it will be rethrown upon joining. - -```purescript -example = do - fiber <- forkAff myAff - killFiber (error "Just had to cancel") fiber - result <- try (joinFiber fiber) - if isLeft result - then (log "Canceled") - else (log "Not Canceled") -``` - -## Parallel Execution - -The `Parallel` instance for `Aff` makes writing parallel computations a breeze. - -Using `parallel` from `Control.Parallel` will turn a regular `Aff` into -`ParAff`. `ParAff` has an `Applicative` instance which will run effects in -parallel, and an `Alternative` instance which will race effects, returning the -one which completes first (canceling the others). To get an `Aff` back, just -run it with `sequential`. - -```purescript --- Make two requests in parallel -example = - sequential $ - Tuple <$> parallel (Ajax.get "https://foo.com") - <*> parallel (Ajax.get "https://bar.com") -``` - -```purescript --- Make a request with a 3 second timeout -example = - sequential $ oneOf - [ parallel (Just <$> Ajax.get "https://foo.com") - , parallel (Nothing <$ delay (Milliseconds 3000.0)) - ] -``` - -```purescript -tvShows = - [ "Stargate_SG-1" - , "Battlestar_Galactica" - , "Farscape" - ] - -getPage page = - Ajax.get $ "https://wikipedia.org/wiki/" <> page - --- Get all pages in parallel -allPages = parTraverse getPage tvShows - --- Get the page that loads the fastest -fastestPage = parOneOfMap getPage tvShows -``` +1. If you encounter a problem or have a question, please [open an issue](https://github.com/purescript-contrib/purescript-aff/issues). We'll do our best to work with you to resolve or answer it. -# API Docs +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. -API documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-aff). +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. diff --git a/bower.json b/bower.json index 61e3ea9..20f27da 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,7 @@ "description": "Asynchronous effect monad", "repository": { "type": "git", - "url": "git://github.com/slamdata/purescript-aff.git" + "url": "https://github.com/slamdata/purescript-aff.git" }, "license": "Apache-2.0", "ignore": [ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..04f30c0 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,201 @@ +# Aff Documentation + +This directory contains documentation for `aff`. If you are interested in contributing new documentation, please read the [contributor guidelines](../CONTRIBUTING.md) and [What Nobody Tells You About Documentation](https://documentation.divio.com) for help getting started. + +> Note that the below documentation examples are partially pseudocode and are unlikely to compile. Working examples can be found [in the tests](https://github.com/purescript-contrib/purescript-aff/blob/master/test/Test/Main.purs). + +## Introduction + +An example of `Aff` is shown below: + +```purescript +deleteBlankLines path = do + contents <- loadFile path + let contents' = S.join "\n" $ A.filter (\a -> S.length a > 0) (S.split "\n" contents) + saveFile path contents' +``` + +This looks like ordinary, synchronous, imperative code, but actually operates asynchronously without any callbacks. Error handling is baked in so you only deal with it when you want to. + +The library contains instances for `Semigroup`, `Monoid`, `Apply`, `Applicative`, `Bind`, `Monad`, `Alt`, `Plus`, `MonadEffect`, `MonadError`, and `Parallel`. These instances allow you to compose asynchronous code as easily as `Effect`, as well as interop with existing `Effect` code. + +## Escaping Callback Hell + +Hopefully, you're using libraries that already use the `Aff` type, so you don't even have to think about callbacks! + +If you're building your own library, then you can make an `Aff` from low-level `Effect` callbacks with `makeAff`. + +```purescript +makeAff :: forall a. ((Either Error a -> Effect Unit) -> Effect Canceler) -> Aff a +``` + +This function expects you to provide a handler, which should call the supplied callback with the result of the asynchronous computation. + +You should also return `Canceler`, which is just a cleanup effect. Since `Aff` threads may be killed, all asynchronous operations should provide a mechanism for unscheduling it. + +`Effect.Aff.Compat` provides functions for easily binding FFI definitions: + +```javascript +exports._ajaxGet = function (request) { // accepts a request + return function (onError, onSuccess) { // and callbacks + var req = doNativeRequest(request, function (err, response) { // make the request + if (err != null) { + onError(err); // invoke the error callback in case of an error + } else { + onSuccess(response); // invoke the success callback with the reponse + } + }); + + // Return a canceler, which is just another Aff effect. + return function (cancelError, cancelerError, cancelerSuccess) { + req.cancel(); // cancel the request + cancelerSuccess(); // invoke the success callback for the canceler + }; + }; +}; +``` + +```purescript +foreign import _ajaxGet :: Request -> EffectFnAff Response +``` + +We can wrap this into an asynchronous computation like so: + +```purescript +ajaxGet :: Request -> Aff Response +ajaxGet = fromEffectFnAff <<< _ajaxGet +``` + +This eliminates callback hell and allows us to write code simply using `do` notation: + +```purescript +example = do + response <- ajaxGet req + log response.body +``` + +## Effect + +All purely synchronous computations (`Effect`) can be lifted to asynchronous computations with `liftEffect` defined in `Effect.Class`. + +```purescript +liftEffect $ log "Hello world!" +``` + +This lets you write your whole program in `Aff`, and still call out to synchronous code. + +## Dealing with Failure + +`Aff` has error handling baked in, so ordinarily you don't have to worry about it. For control-flow exceptions, it's advised to use `ExceptT` instead throwing errors in the `Aff` context. The support for errors mainly exists to notify you when very bad things happen. + +However, when you need to deal with `Aff` errors, you have a few options. + + 1. **Alt** + 2. **MonadError** + 3. **Bracketing** + +#### 1. Alt + +Because `Aff` has an `Alt` instance, you may also use the operator `<|>` to provide an alternative computation in the event of failure: + +```purescript +example = do + result <- Ajax.get "http://foo.com" <|> Ajax.get "http://bar.com" + pure result +``` + +#### 2. MonadError + +`Aff` has a `MonadError` instance, which comes with three functions: `try`, `catchError`, and `throwError`. + +These are defined in [transformers](http://github.com/purescript/purescript-transformers). Here's an example of how you can use them: + +```purescript +tryExample = do + result <- try $ Ajax.get "http://foo.com" + case result of + Left err -> pure "" + Right resp -> pure resp.body + +catchThrowExample = do + resp <- Ajax.get "http://foo.com" `catchError` \_ -> pure defaultResponse + when (resp.statusCode /= 200) do + throwError myErr + pure resp.body +``` + +#### 3. Bracketing + +`Aff` threads can be cancelled, but sometimes we need to guarantee an action gets run even in the presence of exceptions or cancellation. Use `bracket` to acquire resources and clean them up. + +```purescript +example = + bracket + (openFile myFile) + (\file -> closeFile file) + (\file -> appendFile "hello" file) +``` + +In this case, `closeFile` will always be called regardless of exceptions once `openFile` completes. + +## Forking + +Using `forkAff`, you can "fork" an asynchronous computation, which means that its activities will not block the current thread of execution: + +```purescript +forkAff myAff +``` + +Because Javascript is single-threaded, forking does not actually cause the computation to be run in a separate thread. Forking just allows the subsequent actions to execute without waiting for the forked computation to complete. + +Forking returns a `Fiber a`, representing the deferred computation. You can kill a `Fiber` with `killFiber`, which will run any cancelers and cleanup, and you can observe a `Fiber`'s final value with `joinFiber`. If a `Fiber` threw an exception, it will be rethrown upon joining. + +```purescript +example = do + fiber <- forkAff myAff + killFiber (error "Just had to cancel") fiber + result <- try (joinFiber fiber) + if isLeft result + then (log "Canceled") + else (log "Not Canceled") +``` + +## Parallel Execution + +The `Parallel` instance for `Aff` makes writing parallel computations a breeze. + +Using `parallel` from `Control.Parallel` will turn a regular `Aff` into `ParAff`. `ParAff` has an `Applicative` instance which will run effects in parallel, and an `Alternative` instance which will race effects, returning the one which completes first (canceling the others). To get an `Aff` back, just run it with `sequential`. + +```purescript +-- Make two requests in parallel +example = + sequential $ + Tuple <$> parallel (Ajax.get "https://foo.com") + <*> parallel (Ajax.get "https://bar.com") +``` + +```purescript +-- Make a request with a 3 second timeout +example = + sequential $ oneOf + [ parallel (Just <$> Ajax.get "https://foo.com") + , parallel (Nothing <$ delay (Milliseconds 3000.0)) + ] +``` + +```purescript +tvShows = + [ "Stargate_SG-1" + , "Battlestar_Galactica" + , "Farscape" + ] + +getPage page = + Ajax.get $ "https://wikipedia.org/wiki/" <> page + +-- Get all pages in parallel +allPages = parTraverse getPage tvShows + +-- Get the page that loads the fastest +fastestPage = parOneOfMap getPage tvShows +``` diff --git a/package.json b/package.json index 8c9d60a..ca79d85 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,11 @@ { "private": true, "scripts": { - "clean": "rimraf output && rimraf .pulp-cache", - "build": "jshint src --verbose && jscs src && pulp build -- --censor-lib --strict", - "test": "pulp test" + "build": "eslint src && spago build --purs-args '--censor-lib --strict'", + "test": "spago test --no-install" }, "devDependencies": { - "jscs": "^3.0.7", - "jshint": "^2.11.0", - "pulp": "^14.0.0", - "purescript-psa": "^0.7.3", - "rimraf": "^3.0.2" - }, - "jscsConfig": { - "validateIndentation": false + "eslint": "^7.10.0", + "purescript-psa": "^0.8.0" } } diff --git a/packages.dhall b/packages.dhall new file mode 100644 index 0000000..80f5fe6 --- /dev/null +++ b/packages.dhall @@ -0,0 +1,4 @@ +let upstream = + https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20201007/packages.dhall sha256:35633f6f591b94d216392c9e0500207bb1fec42dd355f4fecdfd186956567b6b + +in upstream diff --git a/spago.dhall b/spago.dhall new file mode 100644 index 0000000..dab6263 --- /dev/null +++ b/spago.dhall @@ -0,0 +1,19 @@ +{ name = "aff" +, dependencies = + [ "assert" + , "console" + , "datetime" + , "effect" + , "exceptions" + , "free" + , "functions" + , "minibench" + , "parallel" + , "partial" + , "psci-support" + , "transformers" + , "unsafe-coerce" + ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/src/Effect/Aff.js b/src/Effect/Aff.js index 1928f91..e45a46b 100644 --- a/src/Effect/Aff.js +++ b/src/Effect/Aff.js @@ -1,5 +1,5 @@ /* globals setImmediate, clearImmediate, setTimeout, clearTimeout */ -/* jshint -W083, -W098, -W003 */ +/* eslint-disable no-unused-vars, no-prototype-builtins, no-use-before-define, no-unused-labels, no-param-reassign */ "use strict"; var Aff = function () {