├── .eslintrc.json ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── src ├── Partial.js ├── Partial.purs └── Partial │ ├── Unsafe.js │ └── Unsafe.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 | ## [v4.0.0](https://github.com/purescript/purescript-partial/releases/tag/v4.0.0) - 2022-04-27 16 | 17 | Breaking changes: 18 | - Migrate FFI to ES modules (#24 by @kl0tl and @JordanMartinez) 19 | 20 | New features: 21 | 22 | Bugfixes: 23 | 24 | Other improvements: 25 | 26 | ## [v3.0.0](https://github.com/purescript/purescript-partial/releases/tag/v3.0.0) - 2021-02-26 27 | 28 | Breaking changes: 29 | - Added support for PureScript 0.14 and dropped support for all previous versions (#16) 30 | - Removed deprecated `unsafePartialBecause` (#16) 31 | 32 | New features: 33 | 34 | Bugfixes: 35 | 36 | Other improvements: 37 | - Added extra documentation to `unsafePartial` (#16) 38 | - Removed outer function in `_crashWith` FFI (#16) 39 | - Migrated CI to GitHub Actions and updated installation instructions to use Spago (#17) 40 | - Added a changelog and pull request template (#18) 41 | 42 | ## [v2.0.1](https://github.com/purescript/purescript-partial/releases/tag/v2.0.1) - 2019-02-04 43 | 44 | - Added guide from documentation repo (@anttih) 45 | 46 | ## [v2.0.0](https://github.com/purescript/purescript-partial/releases/tag/v2.0.0) - 2018-05-22 47 | 48 | - Updated for PureScript 0.12 49 | 50 | ## [v1.2.0](https://github.com/purescript/purescript-partial/releases/tag/v1.2.0) - 2016-12-24 51 | 52 | - Added `unsafePartialBecause` (@sharkdp) 53 | 54 | ## [v1.1.2](https://github.com/purescript/purescript-partial/releases/tag/v1.1.2) - 2016-05-15 55 | 56 | - Made a backwards-compatible minor fix for the upcoming PureScript 0.9 release. 57 | 58 | ## [v1.1.1](https://github.com/purescript/purescript-partial/releases/tag/v1.1.1) - 2016-05-02 59 | 60 | - Added a license in bower.json for Pursuit. 61 | 62 | ## [v1.1.0](https://github.com/purescript/purescript-partial/releases/tag/v1.1.0) - 2015-12-18 63 | 64 | - Added `crash`, `crashWith` and `unsafeCrashWith`. 65 | 66 | ## [v1.0.0](https://github.com/purescript/purescript-partial/releases/tag/v1.0.0) - 2015-12-18 67 | 68 | - Initial release 69 | -------------------------------------------------------------------------------- /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-partial 2 | 3 | [![Latest release](http://img.shields.io/github/release/purescript/purescript-partial.svg)](https://github.com/purescript/purescript-partial/releases) 4 | [![Build status](https://github.com/purescript/purescript-partial/workflows/CI/badge.svg?branch=master)](https://github.com/purescript/purescript-partial/actions?query=workflow%3ACI+branch%3Amaster) 5 | [![Pursuit](https://pursuit.purescript.org/packages/purescript-partial/badge)](https://pursuit.purescript.org/packages/purescript-partial) 6 | 7 | Utilities for working with partial functions. 8 | 9 | ## Installation 10 | 11 | ``` 12 | spago install partial 13 | ``` 14 | 15 | ## Why have a Partial type class? 16 | 17 | Every now and then, you will want to use _partial functions;_ that is, 18 | functions which don't handle every possible case of their inputs. For example, 19 | there is a function `fromJust :: ∀ a. Partial ⇒ Maybe a → a` in `Data.Maybe`, 20 | which gives you the value inside a `Just` value, or throws an error if given 21 | `Nothing`. 22 | 23 | It's important that types tell the truth wherever possible, because this is a 24 | large part of what allows us to understand PureScript code easily and refactor 25 | it fearlessly. However, in certain contexts, you know that e.g. an `Either` 26 | value is always going to be `Right`, but you can't prove that to the type 27 | checker, and so you want an escape hatch so that you can write a function that 28 | doesn't have to deal with the `Left` case. This is often the case when 29 | performance is important, for instance. 30 | 31 | Previously, partial functions have been indicated by putting the word "unsafe" 32 | at the start of their names, or by putting them in an "Unsafe" module. For 33 | instance, there was previously an `unsafeIndex` function in 34 | `Data.Array.Unsafe`, and `fromJust` used to be in `Data.Maybe.Unsafe`. However, 35 | this is not ideal, because the fact that these functions are partial, and 36 | therefore unsafe if used carelessly, does not appear in the type. Consequently, 37 | there is little to stop you from using it in an inappropriate manner by 38 | accident. 39 | 40 | The Partial type class allows us to put this information back into the types, 41 | and thereby allows us to clearly demarcate which parts of your code are 42 | responsible for making sure that unsafe functions are used in a safe manner. 43 | 44 | ## I just want to use a partial function, please 45 | 46 | If you try to just use a partial function, you'll most likely get an error 47 | about no instance being found for the `Partial` class. Take this program, for 48 | instance: 49 | 50 | ```purescript 51 | module Main where 52 | 53 | import Prelude 54 | import Data.Maybe (Maybe(..), fromJust) 55 | import Effect (Effect) 56 | import Effect.Console (logShow) 57 | 58 | main :: Effect Unit 59 | main = logShow (fromJust (Just 3)) 60 | ``` 61 | 62 | Because `fromJust` is partial, and because the partiality hasn't been 63 | explicitly handled, you'll get an error: 64 | 65 | ``` 66 | at src/Main.purs line 8, column 1 - line 8, column 56 67 | 68 | No type class instance was found for 69 | 70 | Prim.Partial 71 | ``` 72 | 73 | _Aside: Yes, this is not a fantastic error. It's going to get better soon._ 74 | 75 | The solution is usually to add an application of `unsafePartial` somewhere, 76 | like this: 77 | 78 | ```purescript 79 | module Main where 80 | 81 | import Prelude 82 | import Data.Maybe (Maybe(..), fromJust) 83 | import Effect (Effect) 84 | import Effect.Console (logShow) 85 | import Partial.Unsafe (unsafePartial) 86 | 87 | main :: Effect Unit 88 | main = logShow (unsafePartial (fromJust (Just 3))) 89 | ``` 90 | 91 | ## Where should I put unsafePartial? 92 | 93 | The rule of thumb is to put `unsafePartial` at the level of your program such 94 | that the types tell the truth, and the part of your program responsible for 95 | making sure a use of a partial function is safe is also the part where the 96 | `unsafePartial` is. This is perhaps best demonstrated with an example. 97 | 98 | Imagine that we want to represent vectors in 3D with an array containing 99 | exactly 3 values (perhaps we want to use them with some other API that expects 100 | this representation, and we don't want to be converting back and forth all the 101 | time). In this case, we would usually use a `newtype` and avoid exporting the 102 | constructor: 103 | 104 | ```purescript 105 | module Data.V3 106 | ( V3() 107 | , makeV3 108 | , runV3 109 | ) where 110 | 111 | newtype V3 = V3 (Array Number) 112 | 113 | makeV3 :: Number -> Number -> Number -> V3 114 | makeV3 x y z = V3 [x, y, z] 115 | 116 | runV3 :: V3 -> Array Number 117 | runV3 (V3 v) = v 118 | ``` 119 | 120 | This way, all of the functions are safe; the code will guarantee that any `V3` 121 | does contain exactly 3 values (although the type checker is not aware of this). 122 | 123 | Now imagine we want to write a dot product function: 124 | 125 | ```purescript 126 | dot :: V3 -> V3 -> Number 127 | dot (V3 [x1, x2, x3]) (V3 [y1, y2, y3]) = x1*y1 + x2*y2 + x3*y3 128 | ``` 129 | 130 | We know this is ok, but the compiler disallows it: 131 | 132 | ``` 133 | A case expression could not be determined to cover all inputs. 134 | The following additional cases are required to cover all inputs: 135 | 136 | (V3 _) _ 137 | _ (V3 _) 138 | 139 | Alternatively, add a Partial constraint to the type of the enclosing value. 140 | 141 | in value declaration dot 142 | ``` 143 | 144 | In this case, we can use `unsafePartial` to explicitly say that we don't 145 | actually need to worry about those other cases, and therefore we don't want to 146 | propagate a `Partial` constraint; users of this `dot` function should not have 147 | to worry about this partiality. For example: 148 | 149 | ```purescript 150 | dot :: V3 -> V3 -> Number 151 | dot x y = Partial.Unsafe.unsafePartial (go x y) 152 | where 153 | go :: Partial => V3 -> V3 -> Number 154 | go (V3 [x1, x2, x3]) (V3 [y1, y2, y3]) = x1*y1 + x2*y2 + x3*y3 155 | -- This second pattern can be omitted, but provides a better error message 156 | -- in case we do get an invalid argument at runtime. 157 | go _ _ = Partial.crash "Bad argument: expected exactly 3 elements." 158 | ``` 159 | 160 | The `unsafePartial` function comes from the `Partial.Unsafe` module, in the 161 | `purescript-partial` package. 162 | 163 | In this case, we could also use `Partial.Unsafe.unsafeCrashWith`: 164 | 165 | ```purescript 166 | dot :: V3 -> V3 -> Number 167 | dot (V3 [x1, x2, x3]) (V3 [y1, y2, y3]) = x1*y1 + x2*y2 + x3*y3 168 | dot _ _ = unsafeCrashWith "Bad argument: expected exactly 3 elements." 169 | ``` 170 | 171 | Both implementations will behave in the same way. 172 | 173 | In this case, we know our `dot` implementation is fine, and so users of it 174 | should not have to worry about its partiality, so it makes sense to avoid 175 | propagating the constraint. Now, we will see another case where a `Partial` 176 | constraint _should_ be propagated. 177 | 178 | Let us suppose we want a `foldr1` function, which works in a very similar way 179 | to `foldr` on Lists, except that it doesn't require an initial value to be 180 | passed, and instead requires that the list argument contains at least one 181 | element. 182 | 183 | We can implement it like this: 184 | 185 | ```purescript 186 | foldr1 f (Cons x xs) = foldr f x xs 187 | ``` 188 | 189 | The compiler infers the correct type here, which is: 190 | 191 | ```purescript 192 | foldr1 :: forall a. Partial => (a -> a -> a) -> List a -> a 193 | ``` 194 | 195 | Now imagine we want a version of `Data.Foldable.minimum` which returns an `a` 196 | instead of a `Maybe a`, and is therefore partial. We can implement it in terms 197 | of our new `foldr1` function: 198 | 199 | ```purescript 200 | minimumP = foldr1 min 201 | ``` 202 | 203 | Again, the compiler infers the correct type: 204 | 205 | ```purescript 206 | minimumP :: forall a. (Partial, Ord a) => List a -> a 207 | ``` 208 | 209 | Notice that the `Partial` constraint is automatically propagated to the 210 | `minimumP` function because of the use of another partial function in its 211 | definition, namely `foldr1`. In this case, this is what we want; we should 212 | propagate the `Partial` constraint, because it is still the caller's 213 | responsibility to make sure they supply a non-empty list. 214 | 215 | So hopefully it is now clear why this partiality checking is implemented in 216 | terms of a type class: it allows us to elegantly reuse existing machinery in 217 | the type checker in order to check that a Partial constraint is either 218 | explictly handled or propagated. This should help ensure that when you're 219 | reading the code a few months later, it remains clear which part of the code is 220 | responsible for ensuring that any assumed invariants which cannot be encoded in 221 | the type system do hold. 222 | 223 | ## API Documentation 224 | 225 | - API documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-partial). 226 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-partial", 3 | "homepage": "https://github.com/purescript/purescript-partial", 4 | "description": "Utilities for working with partial functions", 5 | "license": "BSD-3-Clause", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/purescript/purescript-partial.git" 9 | }, 10 | "ignore": [ 11 | "**/.*", 12 | "bower_components", 13 | "node_modules", 14 | "output", 15 | "test", 16 | "bower.json", 17 | "package.json" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "clean": "rimraf output && rimraf .pulp-cache", 5 | "build": "eslint src && pulp build -- --censor-lib --strict", 6 | "test": "pulp test" 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/Partial.js: -------------------------------------------------------------------------------- 1 | // module Partial 2 | 3 | export const _crashWith = function (msg) { 4 | throw new Error(msg); 5 | }; 6 | -------------------------------------------------------------------------------- /src/Partial.purs: -------------------------------------------------------------------------------- 1 | -- | Some partial helper functions. See the README for more documentation. 2 | module Partial 3 | ( crash 4 | , crashWith 5 | ) where 6 | 7 | -- | A partial function which crashes on any input with a default message. 8 | crash :: forall a. Partial => a 9 | crash = crashWith "Partial.crash: partial function" 10 | 11 | -- | A partial function which crashes on any input with the specified message. 12 | crashWith :: forall a. Partial => String -> a 13 | crashWith = _crashWith 14 | 15 | foreign import _crashWith :: forall a. String -> a 16 | -------------------------------------------------------------------------------- /src/Partial/Unsafe.js: -------------------------------------------------------------------------------- 1 | // module Partial.Unsafe 2 | 3 | export const _unsafePartial = function (f) { 4 | return f(); 5 | }; 6 | -------------------------------------------------------------------------------- /src/Partial/Unsafe.purs: -------------------------------------------------------------------------------- 1 | -- | Utilities for working with partial functions. 2 | -- | See the README for more documentation. 3 | module Partial.Unsafe 4 | ( unsafePartial 5 | , unsafeCrashWith 6 | ) where 7 | 8 | import Partial (crashWith) 9 | 10 | -- Note: this function's type signature is more like 11 | -- `(Unit -> a) -> a`. However, we would need to use 12 | -- `unsafeCoerce` to make this compile, incurring 13 | -- either a dependency or reimplementing it here. 14 | -- Rather than doing that, we'll use a type signature 15 | -- of `a -> b` instead. 16 | foreign import _unsafePartial :: forall a b. a -> b 17 | 18 | -- | Discharge a partiality constraint, unsafely. 19 | unsafePartial :: forall a. (Partial => a) -> a 20 | unsafePartial = _unsafePartial 21 | 22 | -- | A function which crashes with the specified error message. 23 | unsafeCrashWith :: forall a. String -> a 24 | unsafeCrashWith msg = unsafePartial (crashWith msg) 25 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Partial (crashWith) 4 | import Partial.Unsafe (unsafePartial) 5 | 6 | f :: Partial => Int -> Int 7 | f 0 = 0 8 | f _ = crashWith "f: partial function" 9 | 10 | safely :: Int 11 | safely = unsafePartial (f 0) 12 | 13 | main :: forall a. a -> {} 14 | main _ = {} 15 | --------------------------------------------------------------------------------