├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── generated-docs └── Control │ └── Fold.md ├── package.json ├── psc-package.json ├── src └── Control │ └── Fold.purs └── test └── Main.purs /.gitignore: -------------------------------------------------------------------------------- 1 | .psci* 2 | bower_components/ 3 | output/ 4 | .psc-package 5 | .psc-ide-port 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | node_js: stable 5 | install: 6 | - npm install -g bower 7 | - npm install 8 | - bower install 9 | script: 10 | - npm run -s build 11 | after_success: 12 | - >- 13 | test $TRAVIS_TAG && 14 | echo $GITHUB_TOKEN | pulp login && 15 | echo y | pulp publish --no-push 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 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 | # purescript-folds 2 | 3 | [![Latest release](http://img.shields.io/bower/v/purescript-folds.svg)](https://github.com/paf31/purescript-folds/releases) 4 | [![Build Status](https://travis-ci.org/paf31/purescript-folds.svg)](https://travis-ci.org/paf31/purescript-folds) 5 | 6 | Applicative Folds, in the style of Gabriel Gonzalez' foldl library 7 | 8 | - [Module Documentation](generated-docs/Control/Fold.md) 9 | - [Example](test/Main.purs) 10 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-folds", 3 | "ignore": [ 4 | "**/.*", 5 | "node_modules", 6 | "bower_components", 7 | "output" 8 | ], 9 | "dependencies": { 10 | "purescript-control": "^4.0.0", 11 | "purescript-profunctor": "^4.0.0", 12 | "purescript-ordered-collections": "^1.6.0" 13 | }, 14 | "devDependencies": { 15 | "purescript-console": "^4.0.0", 16 | "purescript-psci-support": "^4.0.0" 17 | }, 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/paf31/purescript-folds.git" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /generated-docs/Control/Fold.md: -------------------------------------------------------------------------------- 1 | ## Module Control.Fold 2 | 3 | This module provides a type `Fold` for lefts folds, which can be combined 4 | using the `Applicative` class: 5 | 6 | ```purescript 7 | average :: Fold Number Number 8 | average = (/) <$> sum <*> length 9 | ``` 10 | 11 | `Fold` can be used to fold a `Foldable` structure (`foldl`), or scan a 12 | `Traversable` structure (`scanl`): 13 | 14 | ```purescript 15 | finalAverage = foldl average [1.0, 2.0, 3.0] :: Number 16 | movingAverage = scanl average [1.0, 2.0, 3.0] :: Array Number 17 | ``` 18 | 19 | This library is based on the `foldl` library by Gabriel Gonzalez: 20 | 21 | 22 | #### `Fold` 23 | 24 | ``` purescript 25 | newtype Fold a b 26 | ``` 27 | 28 | A left fold, which takes zero or more values of type `a` as input 29 | and produces output of type `b`. 30 | 31 | ##### Instances 32 | ``` purescript 33 | Profunctor Fold 34 | Closed Fold 35 | Functor (Fold a) 36 | Apply (Fold a) 37 | Applicative (Fold a) 38 | Extend (Fold a) 39 | Comonad (Fold a) 40 | (Semigroup b) => Semigroup (Fold a b) 41 | (Monoid b) => Monoid (Fold a b) 42 | ``` 43 | 44 | #### `stepFold` 45 | 46 | ``` purescript 47 | stepFold :: forall a b. a -> Fold a b -> Fold a b 48 | ``` 49 | 50 | Step a fold by providing a single input. 51 | 52 | #### `unfoldFold` 53 | 54 | ``` purescript 55 | unfoldFold :: forall s a b. s -> (s -> a -> s) -> (s -> b) -> Fold a b 56 | ``` 57 | 58 | Create a `Fold` by providing an initial state, a function which updates 59 | that state, and a function which produces output from a state. 60 | 61 | #### `unfoldFold_` 62 | 63 | ``` purescript 64 | unfoldFold_ :: forall a b. b -> (b -> a -> b) -> Fold a b 65 | ``` 66 | 67 | Create a `Fold` by providing an initial state and a function which updates 68 | that state. This is a variant of `unfoldFold` where the output is the state 69 | itself. 70 | 71 | #### `foldl` 72 | 73 | ``` purescript 74 | foldl :: forall f a b. Foldable f => Fold a b -> f a -> b 75 | ``` 76 | 77 | Run a `Fold` by providing a `Foldable` container of inputs, and then 78 | generating a single output. This is analogous to the `foldl` function from 79 | `Data.Foldable`. 80 | 81 | #### `scanl` 82 | 83 | ``` purescript 84 | scanl :: forall f a b. Traversable f => Fold a b -> f a -> f b 85 | ``` 86 | 87 | Run a `Fold` by providing a `Traversable` container of inputs, and 88 | generating an output for each input. This is analogous to the `scanl` function from 89 | `Data.Traversable`. 90 | 91 | #### `mconcat` 92 | 93 | ``` purescript 94 | mconcat :: forall m. Monoid m => Fold m m 95 | ``` 96 | 97 | `Fold` values in some `Monoid`. 98 | 99 | #### `head` 100 | 101 | ``` purescript 102 | head :: forall a. Fold a (Maybe a) 103 | ``` 104 | 105 | A `Fold` which remembers the first input. 106 | 107 | #### `last` 108 | 109 | ``` purescript 110 | last :: forall a. Fold a (Maybe a) 111 | ``` 112 | 113 | A `Fold` which keeps the last input. 114 | 115 | #### `null` 116 | 117 | ``` purescript 118 | null :: forall a. Fold a Boolean 119 | ``` 120 | 121 | A `Fold` which tests whether any inputs were seen. 122 | 123 | #### `length` 124 | 125 | ``` purescript 126 | length :: forall a s. Semiring s => Fold a s 127 | ``` 128 | 129 | A `Fold` which counts its inputs. 130 | 131 | #### `and` 132 | 133 | ``` purescript 134 | and :: forall b. HeytingAlgebra b => Fold b b 135 | ``` 136 | 137 | A `Fold` which tests if _all_ of its inputs were true 138 | (generalized to work with an arbitrary `HeytingAlgebra`). 139 | 140 | #### `or` 141 | 142 | ``` purescript 143 | or :: forall b. HeytingAlgebra b => Fold b b 144 | ``` 145 | 146 | A `Fold` which tests if _any_ of its inputs were true 147 | (generalized to work with an arbitrary `HeytingAlgebra`). 148 | 149 | #### `any` 150 | 151 | ``` purescript 152 | any :: forall a b. HeytingAlgebra b => (a -> b) -> Fold a b 153 | ``` 154 | 155 | A `Fold` which tests if _any_ of its inputs satisfy some predicate 156 | (generalized to work with an arbitrary `HeytingAlgebra`). 157 | 158 | #### `all` 159 | 160 | ``` purescript 161 | all :: forall a b. HeytingAlgebra b => (a -> b) -> Fold a b 162 | ``` 163 | 164 | A `Fold` which tests if _all_ of its inputs satisfy some predicate 165 | (generalized to work with an arbitrary `HeytingAlgebra`). 166 | 167 | #### `sum` 168 | 169 | ``` purescript 170 | sum :: forall s. Semiring s => Fold s s 171 | ``` 172 | 173 | A `Fold` which computes the sum of its inputs 174 | (generalized to work with an arbitrary `Semiring`). 175 | 176 | #### `product` 177 | 178 | ``` purescript 179 | product :: forall s. Semiring s => Fold s s 180 | ``` 181 | 182 | A `Fold` which computes the product of its inputs 183 | (generalized to work with an arbitrary `Semiring`). 184 | 185 | #### `maximum` 186 | 187 | ``` purescript 188 | maximum :: forall a. Bounded a => Fold a a 189 | ``` 190 | 191 | A `Fold` which computes the maximum of its inputs 192 | (generalized to work with an arbitrary `Bounded` type). 193 | 194 | #### `minimum` 195 | 196 | ``` purescript 197 | minimum :: forall a. Bounded a => Fold a a 198 | ``` 199 | 200 | A `Fold` which computes the minimum of its inputs 201 | (generalized to work with an arbitrary `Bounded` type). 202 | 203 | #### `elem` 204 | 205 | ``` purescript 206 | elem :: forall a. Eq a => a -> Fold a Boolean 207 | ``` 208 | 209 | A `Fold` which tests if a specific value appeared as an input. 210 | 211 | #### `notElem` 212 | 213 | ``` purescript 214 | notElem :: forall a. Eq a => a -> Fold a Boolean 215 | ``` 216 | 217 | A `Fold` which tests if a specific value did not appear as an input. 218 | 219 | #### `distributed` 220 | 221 | ``` purescript 222 | distributed :: forall f a b. Distributive f => Fold a b -> Fold (f a) (f b) 223 | ``` 224 | 225 | Fold over entire collections of inputs, producing a collection of outputs. 226 | 227 | 228 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "clean": "rimraf output && rimraf .pulp-cache", 5 | "build": "pulp build && pulp test" 6 | }, 7 | "devDependencies": { 8 | "pulp": "^12.3.0", 9 | "purescript-psa": "^0.5.0", 10 | "purescript": "^0.12.0", 11 | "rimraf": "^2.5.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /psc-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "folds", 3 | "source": "https://github.com/purescript/package-sets.git", 4 | "set": "psc-0.12.3-20190312", 5 | "depends": [ 6 | "bifunctors", 7 | "control", 8 | "distributive", 9 | "either", 10 | "foldable-traversable", 11 | "identity", 12 | "invariant", 13 | "maybe", 14 | "newtype", 15 | "ordered-collections", 16 | "prelude", 17 | "profunctor", 18 | "tuples" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/Control/Fold.purs: -------------------------------------------------------------------------------- 1 | -- | This module provides a type `Fold` for lefts folds, which can be combined 2 | -- | using the `Applicative` class: 3 | -- | 4 | -- | ```purescript 5 | -- | average :: Fold Number Number 6 | -- | average = (/) <$> sum <*> length 7 | -- | ``` 8 | -- | 9 | -- | `Fold` can be used to fold a `Foldable` structure (`foldl`), or scan a 10 | -- | `Traversable` structure (`scanl`): 11 | -- | 12 | -- | ```purescript 13 | -- | finalAverage = foldl average [1.0, 2.0, 3.0] :: Number 14 | -- | movingAverage = scanl average [1.0, 2.0, 3.0] :: Array Number 15 | -- | ``` 16 | -- | 17 | -- | This library is based on the `foldl` library by Gabriel Gonzalez: 18 | -- | 19 | 20 | module Control.Fold 21 | ( Fold 22 | , stepFold 23 | , unfoldFold 24 | , unfoldFold_ 25 | , foldl 26 | , scanl 27 | , mconcat 28 | , head 29 | , last 30 | , null 31 | , length 32 | , and 33 | , or 34 | , any 35 | , all 36 | , sum 37 | , product 38 | , maximum 39 | , minimum 40 | , elem 41 | , notElem 42 | , distributed 43 | , groupBy 44 | , prefilter 45 | ) where 46 | 47 | import Prelude 48 | import Data.Foldable as Foldable 49 | import Data.Traversable as Traversable 50 | import Control.Alt ((<|>)) 51 | import Control.Apply (lift2) 52 | import Control.Comonad (class Comonad, extract) 53 | import Control.Extend (class Extend) 54 | import Data.Distributive (class Distributive, cotraverse) 55 | import Data.Foldable (class Foldable) 56 | import Data.Function (applyFlipped) 57 | import Data.HeytingAlgebra (ff, tt) 58 | import Data.Map (Map, alter) 59 | import Data.Maybe (Maybe(..), fromMaybe) 60 | import Data.Profunctor (class Profunctor, dimap, lcmap) 61 | import Data.Profunctor.Closed (class Closed, closed) 62 | import Data.Traversable (class Traversable) 63 | 64 | -- | A left fold, which takes zero or more values of type `a` as input 65 | -- | and produces output of type `b`. 66 | newtype Fold a b = Fold { step :: a -> Fold a b, finish :: Unit -> b } 67 | 68 | -- | Step a fold by providing a single input. 69 | stepFold :: forall a b. a -> Fold a b -> Fold a b 70 | stepFold a (Fold o) = o.step a 71 | 72 | -- | Create a `Fold` by providing an initial state, a function which updates 73 | -- | that state, and a function which produces output from a state. 74 | unfoldFold :: forall s a b. s -> (s -> a -> s) -> (s -> b) -> Fold a b 75 | unfoldFold s0 step finish = go s0 76 | where 77 | go :: s -> Fold a b 78 | go s = Fold { step: go <<< step s, finish: \_ -> finish s } 79 | 80 | -- | Create a `Fold` by providing an initial state and a function which updates 81 | -- | that state. This is a variant of `unfoldFold` where the output is the state 82 | -- | itself. 83 | unfoldFold_ :: forall a b. b -> (b -> a -> b) -> Fold a b 84 | unfoldFold_ s0 step = unfoldFold s0 step identity 85 | 86 | -- | Run a `Fold` by providing a `Foldable` container of inputs, and then 87 | -- | generating a single output. This is analogous to the `foldl` function from 88 | -- | `Data.Foldable`. 89 | foldl :: forall f a b. Foldable f => Fold a b -> f a -> b 90 | foldl fold xs = extract (Foldable.foldl (\(Fold o) -> o.step) fold xs) 91 | 92 | -- | Run a `Fold` by providing a `Traversable` container of inputs, and 93 | -- | generating an output for each input. This is analogous to the `scanl` function from 94 | -- | `Data.Traversable`. 95 | scanl :: forall f a b. Traversable f => Fold a b -> f a -> f b 96 | scanl fold xs = map extract (Traversable.scanl (\(Fold o) -> o.step) fold xs) 97 | 98 | -- | Fold over entire collections of inputs, producing a collection of outputs. 99 | distributed :: forall f a b. Distributive f => Fold a b -> Fold (f a) (f b) 100 | distributed = dimap applyFlipped (flip cotraverse identity) <<< closed 101 | 102 | -- | `Fold` values in some `Monoid`. 103 | mconcat :: forall m. Monoid m => Fold m m 104 | mconcat = unfoldFold_ mempty append 105 | 106 | -- | A `Fold` which remembers the first input. 107 | head :: forall a. Fold a (Maybe a) 108 | head = unfoldFold_ Nothing (\m a -> m <|> Just a) 109 | 110 | -- | A `Fold` which keeps the last input. 111 | last :: forall a. Fold a (Maybe a) 112 | last = unfoldFold_ Nothing (\_ a -> Just a) 113 | 114 | -- | A `Fold` which tests whether any inputs were seen. 115 | null :: forall a. Fold a Boolean 116 | null = unfoldFold_ true (\_ _ -> false) 117 | 118 | -- | A `Fold` which counts its inputs. 119 | length :: forall a s. Semiring s => Fold a s 120 | length = unfoldFold_ zero (\n _ -> n + one) 121 | 122 | -- | A `Fold` which tests if _all_ of its inputs were true 123 | -- | (generalized to work with an arbitrary `HeytingAlgebra`). 124 | and :: forall b. HeytingAlgebra b => Fold b b 125 | and = unfoldFold_ tt conj 126 | 127 | -- | A `Fold` which tests if _any_ of its inputs were true 128 | -- | (generalized to work with an arbitrary `HeytingAlgebra`). 129 | or :: forall b. HeytingAlgebra b => Fold b b 130 | or = unfoldFold_ ff disj 131 | 132 | -- | A `Fold` which tests if _all_ of its inputs satisfy some predicate 133 | -- | (generalized to work with an arbitrary `HeytingAlgebra`). 134 | all :: forall a b. HeytingAlgebra b => (a -> b) -> Fold a b 135 | all pred = lcmap pred and 136 | 137 | -- | A `Fold` which tests if _any_ of its inputs satisfy some predicate 138 | -- | (generalized to work with an arbitrary `HeytingAlgebra`). 139 | any :: forall a b. HeytingAlgebra b => (a -> b) -> Fold a b 140 | any pred = lcmap pred or 141 | 142 | -- | A `Fold` which computes the sum of its inputs 143 | -- | (generalized to work with an arbitrary `Semiring`). 144 | sum :: forall s. Semiring s => Fold s s 145 | sum = unfoldFold_ zero add 146 | 147 | -- | A `Fold` which computes the product of its inputs 148 | -- | (generalized to work with an arbitrary `Semiring`). 149 | product :: forall s. Semiring s => Fold s s 150 | product = unfoldFold_ one mul 151 | 152 | -- | A `Fold` which computes the maximum of its inputs 153 | -- | (generalized to work with an arbitrary `Bounded` type). 154 | maximum :: forall a. Bounded a => Fold a a 155 | maximum = unfoldFold_ bottom max 156 | 157 | -- | A `Fold` which computes the minimum of its inputs 158 | -- | (generalized to work with an arbitrary `Bounded` type). 159 | minimum :: forall a. Bounded a => Fold a a 160 | minimum = unfoldFold_ top min 161 | 162 | -- | A `Fold` which tests if a specific value appeared as an input. 163 | elem :: forall a. Eq a => a -> Fold a Boolean 164 | elem a = any (_ == a) 165 | 166 | -- | A `Fold` which tests if a specific value did not appear as an input. 167 | notElem :: forall a. Eq a => a -> Fold a Boolean 168 | notElem a = all (_ /= a) 169 | 170 | -- | Perform a `Fold` while grouping the data according to a specified 171 | -- | group projection function. Returns the folded result grouped as a 172 | -- | map keyed by the group. 173 | groupBy :: forall a r g . Ord g => (a -> g) -> Fold a r -> Fold a (Map g r) 174 | groupBy grouper f1 = unfoldFold mempty combine (map extract) 175 | where 176 | combine m x = alter (pure <<< stepFold x <<< fromMaybe f1) (grouper x) m 177 | 178 | -- | `(prefilter pred f)` returns a new Fold based on `f` but where 179 | -- | inputs will only be included if they satisfy a predicate `pred`. 180 | prefilter :: forall a b . (a -> Boolean) -> Fold a b -> Fold a b 181 | prefilter pred f = unfoldFold f maybeStep extract 182 | where maybeStep s v = if pred v then stepFold v s else s 183 | 184 | instance profunctorFold :: Profunctor Fold where 185 | dimap f g (Fold o) = Fold { step, finish } 186 | where 187 | step = f >>> o.step >>> dimap f g 188 | finish = o.finish >>> g 189 | 190 | instance closedFold :: Closed Fold where 191 | closed f = unfoldFold (const f) (lift2 (flip stepFold)) (extract <<< _) 192 | 193 | instance functorFold :: Functor (Fold a) where 194 | map f (Fold o) = Fold { step, finish } 195 | where 196 | step = o.step >>> map f 197 | finish = o.finish >>> f 198 | 199 | instance applyFold :: Apply (Fold a) where 200 | apply (Fold f) (Fold x) = Fold { step, finish } 201 | where 202 | step a = apply (f.step a) (x.step a) 203 | finish u = (f.finish u) (x.finish u) 204 | 205 | instance applicativeFold :: Applicative (Fold a) where 206 | pure b = done 207 | where 208 | done = Fold { step: \_ -> done, finish: \_ -> b } 209 | 210 | instance extendFold :: Extend (Fold a) where 211 | extend f = map f <<< dup 212 | where 213 | dup fold@(Fold o) = Fold { step, finish } 214 | where 215 | step a = dup (o.step a) 216 | finish _ = fold 217 | 218 | instance comonadFold :: Comonad (Fold a) where 219 | extract (Fold o) = o.finish unit 220 | 221 | instance semigroupFold :: Semigroup b => Semigroup (Fold a b) where 222 | append = lift2 append 223 | 224 | instance monoidFold :: Monoid b => Monoid (Fold a b) where 225 | mempty = pure mempty 226 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | import Control.Fold (Fold, scanl, length, sum) 5 | import Effect (Effect) 6 | import Effect.Console (logShow) 7 | 8 | main :: Effect Unit 9 | main = logShow (scanl average [1.0, 2.0, 3.0, 4.0, 5.0]) 10 | where 11 | average :: forall s. EuclideanRing s => Fold s s 12 | average = (/) <$> sum <*> length 13 | --------------------------------------------------------------------------------