├── .eslintrc.json ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── src └── Effect │ ├── Ref.js │ └── Ref.purs └── test └── Main.purs /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "strict": [2, "global"], 9 | "block-scoped-var": 2, 10 | "consistent-return": 2, 11 | "eqeqeq": [2, "smart"], 12 | "guard-for-in": 2, 13 | "no-caller": 2, 14 | "no-extend-native": 2, 15 | "no-loop-func": 2, 16 | "no-new": 2, 17 | "no-param-reassign": 2, 18 | "no-return-assign": 2, 19 | "no-unused-expressions": 2, 20 | "no-use-before-define": 2, 21 | "radix": [2, "always"], 22 | "indent": [2, 2], 23 | "quotes": [2, "double"], 24 | "semi": [2, "always"] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description of the change** 2 | 3 | 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. 4 | 5 | --- 6 | 7 | **Checklist:** 8 | 9 | - [ ] Added the change to the changelog's "Unreleased" section with a reference to this PR (e.g. "- Made a change (#0000)") 10 | - [ ] Linked any existing issues or proposals that this pull request should close 11 | - [ ] Updated or added relevant documentation 12 | - [ ] Added a test for the contribution (if applicable) 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - uses: purescript-contrib/setup-purescript@main 16 | with: 17 | purescript: "unstable" 18 | 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: "14.x" 22 | 23 | - name: Install dependencies 24 | run: | 25 | npm install -g bower 26 | npm install 27 | bower install --production 28 | 29 | - name: Build source 30 | run: npm run-script build 31 | 32 | - name: Run tests 33 | run: | 34 | bower install 35 | npm run-script test --if-present 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | !/.gitignore 3 | !/.eslintrc.json 4 | !/.github/ 5 | /bower_components/ 6 | /node_modules/ 7 | /output/ 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /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 | ## [v6.0.0](https://github.com/purescript/purescript-refs/releases/tag/v6.0.0) - 2022-04-27 16 | 17 | Breaking changes: 18 | - Migrate FFI to ES modules (#39 by @kl0tl and @JordanMartinez) 19 | 20 | New features: 21 | 22 | Bugfixes: 23 | 24 | Other improvements: 25 | 26 | ## [v5.0.0](https://github.com/purescript/purescript-refs/releases/tag/v5.0.0) - 2021-02-26 27 | 28 | Breaking changes: 29 | - Added support for PureScript 0.14 and dropped support for all previous versions (#29) 30 | 31 | New features: 32 | - Allow the construction of self-referential `Ref`s (#21) 33 | - Add roles declarations to allow safe coercions (#29) 34 | 35 | Bugfixes: 36 | 37 | Other improvements: 38 | - Add some examples and tests and update documentation (#19, #20, #31) 39 | - Remove primes from foreign module exports in preparation for ES modules (#24) 40 | - Remove `return {}` from FFI implementations for a small performance boost (#27) 41 | - Migrated CI to GitHub Actions and updated installation instructions to use Spago (#33) 42 | - Added a changelog and pull request template (#34, #35) 43 | 44 | ## [v4.1.0](https://github.com/purescript/purescript-refs/releases/tag/v4.1.0) - 2018-05-26 45 | 46 | - Added `modify_` - `modify` with a `Unit` return (@justinwoo) 47 | 48 | ## [v4.0.0](https://github.com/purescript/purescript-refs/releases/tag/v4.0.0) - 2018-05-22 49 | 50 | - Updated for PureScript 0.12 51 | - Names have been shortened to drop the `Ref` prefix for less repetition with qualified imports 52 | - The argument order of functions has changed so the `Ref` is always in the last position 53 | 54 | ## [v3.0.0](https://github.com/purescript/purescript-refs/releases/tag/v3.0.0) - 2017-03-26 55 | 56 | - Updated for PureScript 0.11 57 | 58 | ## [v2.0.0](https://github.com/purescript/purescript-refs/releases/tag/v2.0.0) - 2016-10-02 59 | 60 | - Updated dependencies 61 | 62 | ## [v1.0.0](https://github.com/purescript/purescript-refs/releases/tag/v1.0.0) - 2016-06-01 63 | 64 | This release is intended for the PureScript 0.9.1 compiler and newer. 65 | 66 | **Note**: The v1.0.0 tag is not meant to indicate the library is “finished”, the core libraries are all being bumped to this for the 0.9 compiler release so as to use semver more correctly. 67 | 68 | ## [v0.2.0](https://github.com/purescript/purescript-refs/releases/tag/v0.2.0) - 2015-06-30 69 | 70 | - This release works with versions 0.7.\* of the PureScript compiler. It will not work with older versions. If you are using an older version, you should require an older, compatible version of this library. 71 | 72 | ## [v0.2.0-rc.1](https://github.com/purescript/purescript-refs/releases/tag/v0.2.0-rc.1) - 2015-06-09 73 | 74 | - Initial release candidate of the library intended for the 0.7 compiler. 75 | 76 | ## [v0.1.3](https://github.com/purescript/purescript-refs/releases/tag/v0.1.3) - 2015-03-19 77 | 78 | - Updated docs 79 | 80 | ## [v0.1.2](https://github.com/purescript/purescript-refs/releases/tag/v0.1.2) - 2014-11-24 81 | 82 | - Added `modifyRef` that returns a pure value (#2) 83 | 84 | ## [v0.1.1](https://github.com/purescript/purescript-refs/releases/tag/v0.1.1) - 2014-06-14 85 | 86 | - Now uses "proper" `Unit` type instead of `{}` (garyb) 87 | 88 | ## [v0.1.0](https://github.com/purescript/purescript-refs/releases/tag/v0.1.0) - 2014-04-25 89 | 90 | - Initial release 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 PureScript 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-refs 2 | 3 | [![Latest release](http://img.shields.io/github/release/purescript/purescript-refs.svg)](https://github.com/purescript/purescript-refs/releases) 4 | [![Build status](https://github.com/purescript/purescript-refs/workflows/CI/badge.svg?branch=master)](https://github.com/purescript/purescript-refs/actions?query=workflow%3ACI+branch%3Amaster) 5 | [![Pursuit](https://pursuit.purescript.org/packages/purescript-refs/badge)](https://pursuit.purescript.org/packages/purescript-refs) 6 | 7 | This module defines functions for working with mutable value references. 8 | 9 | _Note_: [`Control.Monad.ST`](https://pursuit.purescript.org/packages/purescript-st/4.0.0/docs/Control.Monad.ST) provides a _safe_ alternative to `Ref` when mutation is restricted to a local scope. 10 | 11 | ## Installation 12 | 13 | ``` 14 | spago install refs 15 | ``` 16 | 17 | ## Example 18 | 19 | ```purs 20 | import Effect.Ref as Ref 21 | 22 | main = do 23 | -- initialize a new Ref with the value 0 24 | ref <- Ref.new 0 25 | 26 | -- read from it and check it 27 | curr1 <- Ref.read ref 28 | assertEqual { actual: curr1, expected: 0 } 29 | 30 | -- write over the ref with 1 31 | Ref.write 1 ref 32 | 33 | -- now it is 1 when we read out the value 34 | curr2 <- Ref.read ref 35 | assertEqual { actual: curr2, expected: 1 } 36 | 37 | -- modify it by adding 1 to the current state 38 | Ref.modify_ (\s -> s + 1) ref 39 | 40 | -- now it is 2 when we read out the value 41 | curr3 <- Ref.read ref 42 | assertEqual { actual: curr3, expected: 2 } 43 | ``` 44 | 45 | See [tests](test/Main.purs) to see usages. 46 | 47 | ## Documentation 48 | 49 | Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-refs). 50 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-refs", 3 | "homepage": "https://github.com/purescript/purescript-refs", 4 | "license": "BSD-3-Clause", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/purescript/purescript-refs.git" 8 | }, 9 | "ignore": [ 10 | "**/.*", 11 | "bower_components", 12 | "node_modules", 13 | "output", 14 | "test", 15 | "bower.json", 16 | "package.json" 17 | ], 18 | "dependencies": { 19 | "purescript-effect": "^4.0.0", 20 | "purescript-prelude": "^6.0.0" 21 | }, 22 | "devDependencies": { 23 | "purescript-assert": "^6.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "clean": "rimraf output && rimraf .pulp-cache", 5 | "test": "pulp test", 6 | "build": "eslint src && pulp build -- --censor-lib --strict" 7 | }, 8 | "devDependencies": { 9 | "eslint": "^7.15.0", 10 | "pulp": "16.0.0-0", 11 | "purescript-psa": "^0.8.2", 12 | "rimraf": "^3.0.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Effect/Ref.js: -------------------------------------------------------------------------------- 1 | export const _new = function (val) { 2 | return function () { 3 | return { value: val }; 4 | }; 5 | }; 6 | 7 | export const newWithSelf = function (f) { 8 | return function () { 9 | var ref = { value: null }; 10 | ref.value = f(ref); 11 | return ref; 12 | }; 13 | }; 14 | 15 | export const read = function (ref) { 16 | return function () { 17 | return ref.value; 18 | }; 19 | }; 20 | 21 | export const modifyImpl = function (f) { 22 | return function (ref) { 23 | return function () { 24 | var t = f(ref.value); 25 | ref.value = t.state; 26 | return t.value; 27 | }; 28 | }; 29 | }; 30 | 31 | export const write = function (val) { 32 | return function (ref) { 33 | return function () { 34 | ref.value = val; 35 | }; 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /src/Effect/Ref.purs: -------------------------------------------------------------------------------- 1 | -- | This module defines the `Ref` type for mutable value references, as well 2 | -- | as actions for working with them. 3 | -- | 4 | -- | You'll notice that all of the functions that operate on a `Ref` (e.g. 5 | -- | `new`, `read`, `write`) return their result wrapped in an `Effect`. 6 | -- | Working with mutable references is considered effectful in PureScript 7 | -- | because of the principle of purity: functions should not have side 8 | -- | effects, and should return the same result when called with the same 9 | -- | arguments. If a `Ref` could be written to without using `Effect`, that 10 | -- | would cause a side effect (the effect of changing the result of subsequent 11 | -- | reads for that `Ref`). If there were a function for reading the current 12 | -- | value of a `Ref` without the result being wrapped in `Effect`, the result 13 | -- | of calling that function would change each time a new value was written to 14 | -- | the `Ref`. Even creating a new `Ref` is effectful: if there were a 15 | -- | function for creating a new `Ref` with the type `forall s. s -> Ref s`, 16 | -- | then calling that function twice with the same argument would not give the 17 | -- | same result in each case, since you'd end up with two distinct references 18 | -- | which could be updated independently of each other. 19 | -- | 20 | -- | _Note_: `Control.Monad.ST` provides a pure alternative to `Ref` when 21 | -- | mutation is restricted to a local scope. 22 | module Effect.Ref 23 | ( Ref 24 | , new 25 | , newWithSelf 26 | , read 27 | , modify' 28 | , modify 29 | , modify_ 30 | , write 31 | ) where 32 | 33 | import Prelude 34 | 35 | import Effect (Effect) 36 | 37 | -- | A value of type `Ref a` represents a mutable reference 38 | -- | which holds a value of type `a`. 39 | foreign import data Ref :: Type -> Type 40 | 41 | type role Ref representational 42 | 43 | -- | Create a new mutable reference containing the specified value. 44 | foreign import _new :: forall s. s -> Effect (Ref s) 45 | 46 | new :: forall s. s -> Effect (Ref s) 47 | new = _new 48 | 49 | -- | Create a new mutable reference containing a value that can refer to the 50 | -- | `Ref` being created. 51 | foreign import newWithSelf :: forall s. (Ref s -> s) -> Effect (Ref s) 52 | 53 | -- | Read the current value of a mutable reference. 54 | foreign import read :: forall s. Ref s -> Effect s 55 | 56 | -- | Update the value of a mutable reference by applying a function 57 | -- | to the current value. 58 | modify' :: forall s b. (s -> { state :: s, value :: b }) -> Ref s -> Effect b 59 | modify' = modifyImpl 60 | 61 | foreign import modifyImpl :: forall s b. (s -> { state :: s, value :: b }) -> Ref s -> Effect b 62 | 63 | -- | Update the value of a mutable reference by applying a function 64 | -- | to the current value. The updated value is returned. 65 | modify :: forall s. (s -> s) -> Ref s -> Effect s 66 | modify f = modify' \s -> let s' = f s in { state: s', value: s' } 67 | 68 | -- | A version of `modify` which does not return the updated value. 69 | modify_ :: forall s. (s -> s) -> Ref s -> Effect Unit 70 | modify_ f s = void $ modify f s 71 | 72 | -- | Update the value of a mutable reference to the specified value. 73 | foreign import write :: forall s. s -> Ref s -> Effect Unit 74 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Ref as Ref 7 | import Test.Assert (assertEqual) 8 | 9 | main :: Effect Unit 10 | main = do 11 | -- initialize a new Ref with the value 0 12 | ref <- Ref.new 0 13 | 14 | -- read from it and check it 15 | curr1 <- Ref.read ref 16 | assertEqual { actual: curr1, expected: 0 } 17 | 18 | -- write over the ref with 1 19 | Ref.write 1 ref 20 | 21 | -- now it is 1 when we read out the value 22 | curr2 <- Ref.read ref 23 | assertEqual { actual: curr2, expected: 1 } 24 | 25 | -- modify it by adding 1 to the current state 26 | Ref.modify_ (\s -> s + 1) ref 27 | 28 | -- now it is 2 when we read out the value 29 | curr3 <- Ref.read ref 30 | assertEqual { actual: curr3, expected: 2 } 31 | 32 | selfRef 33 | 34 | newtype RefBox = RefBox { ref :: Ref.Ref RefBox, value :: Int } 35 | 36 | selfRef :: Effect Unit 37 | selfRef = do 38 | -- Create a self-referential `Ref` 39 | ref <- Ref.newWithSelf \ref -> RefBox { ref, value: 0 } 40 | 41 | -- Grab the `Ref` from within the `Ref` 42 | ref' <- Ref.read ref <#> \(RefBox r) -> r.ref 43 | 44 | -- Modify the `ref` and check that value in `ref'` changes 45 | Ref.modify_ (\(RefBox r) -> RefBox (r { value = 1 })) ref 46 | assertEqual 47 | <<< { expected: 1, actual: _ } 48 | =<< (Ref.read ref' <#> \(RefBox { value }) -> value) 49 | 50 | -- Modify the `ref'` and check that value in `ref` changes 51 | Ref.modify_ (\(RefBox r) -> RefBox (r { value = 2 })) ref' 52 | assertEqual 53 | <<< { expected: 2, actual: _ } 54 | =<< (Ref.read ref <#> \(RefBox { value }) -> value) 55 | --------------------------------------------------------------------------------