├── .gitignore ├── LICENSE ├── README.md ├── elm.json ├── examples ├── Euler14.elm ├── Euler92.elm ├── Fibonacci.elm ├── SieveOfEratosthenes.elm └── elm-package.json ├── src └── State.elm └── tests ├── Tests.elm └── elm-package.json /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-present, Folkert de Vries 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Threading state through computation 2 | =================================== 3 | 4 | This library provides ways to compose functions of the type 5 | `s -> (a, s)`. 6 | 7 | ## Motivation 8 | 9 | From time to time, you'll see a pattern like this in your code 10 | 11 | ```elm 12 | (newValue, newState) = f state 13 | (newerValue, newerState) = g newValue newState 14 | (newererValue, newererState) = h newerValue newerState 15 | ``` 16 | 17 | The above pattern is ugly and error-prone (because of typo's, for instance). 18 | It can be abstracted by creating a function that composes `f` and `g`, that is 19 | the output of `f` is the input to `g`. 20 | 21 | ```elm 22 | f : s -> (a, s) 23 | g : a -> s -> (a, s) 24 | ``` 25 | 26 | This library implements the described composition and provides a bunch of helper functions for 27 | working with State. For a more in-depth explanation of how the implementation works, see the [derivation](#derivation). 28 | 29 | *This library is advanced and relatively abstract. 30 | If anything is unclear, please [open an issue](https://github.com/folkertdev/elm-state/issues)*. 31 | 32 | ## Working with values within State 33 | 34 | There are three main functions for working with the wrapped state value: 35 | 36 | * **State.get**: aquire the state value. 37 | ```elm 38 | State.map2 (+) State.get State.get 39 | |> State.run 42 40 | -- == (84, 42) 41 | ``` 42 | 43 | * **State.put**: set the state to a certain value. 44 | ```elm 45 | State.put 5 46 | |> State.run 3 47 | -- == ((), 5) 48 | ``` 49 | Note that `State.put` discards the value currently stored in the state. 50 | 51 | * **State.modify**: apply a function to the state; a combination of get and set 52 | ```elm 53 | State.modify (\v -> v + 1) 54 | |> State.map (\_ -> "finished") 55 | |> State.run 42 56 | -- == ("finished", 43) 57 | ``` 58 | Note that `State.modify` (because it combines get and put) discards 59 | the value currently stored in the state. 60 | 61 | 62 | ## Extracting results from State 63 | 64 | Remember, we really build a large `s -> (a, s)` function out of smaller components. To run this function, 65 | we need an initial state, and get a final state and a final value. The function `State.run` does just that. 66 | 67 | ```elm 68 | run : s -> State s a -> (a, s) 69 | run initialState (State f) = 70 | f initialState 71 | ``` 72 | 73 | ## Structuring computation with andThen 74 | 75 | The composition operator for functions wrapped in `State` is called `andThen`. It is the primary way 76 | to structure computations that involve `State`. When not used with care, this can lead to truly awful code. 77 | This is what the Haskellers at Facebook call [a code tornado](https://youtu.be/mlTO510zO78?t=34m7s): 78 | 79 | ```elm 80 | cycle : Int -> State (Array Bool) (Maybe Int) 81 | cycle n = 82 | let 83 | mark : Int -> State (Array Bool) () 84 | mark n = 85 | State.modify (Array.set n False) 86 | 87 | multiplesToMark length n = 88 | range (n * n) length n 89 | 90 | in 91 | State.map Array.length State.get 92 | |> andThen (\length -> 93 | State.mapState mark (multiplesToMark length n) 94 | |> andThen (\_ -> 95 | State.map (toNextIndex n) State.get 96 | ) 97 | ) 98 | ``` 99 | 100 | This problem can be solved by extracting subcomputations and giving them a descriptive name. Not only is the final composition 101 | (in the 'in' clause) very readable and understandable, the individual components are also nicely reusable. 102 | 103 | ```elm 104 | cycle : Int -> State (Array Bool) (Maybe Int) 105 | cycle n = 106 | let 107 | mark : Int -> State (Array Bool) () 108 | mark n = 109 | State.modify (Array.set n False) 110 | 111 | multiplesToMark length n = 112 | range (n * n) length n 113 | 114 | getArrayLength = 115 | State.map Array.length State.get 116 | 117 | markMultiples length = 118 | -- mapState shares the Array Int between invocations of mark. 119 | State.mapState mark (multiplesToMark length n) 120 | 121 | setNextIndex _ = 122 | State.map (toNextIndex n) State.get 123 | in 124 | getArrayLength 125 | |> andThen markMultiples 126 | |> andThen setNextIndex 127 | ``` 128 | 129 | When using andThen, try to break up your computation into small, reusable bits and give them a descriptive name. 130 | A general Elm principle is that the shortest code is often not the best code. Don't take shortcuts with 131 | andThen in production code. 132 | 133 | 134 | 135 | ### Tips 136 | 137 | * **Prevent code tornadoes** 138 | * **Name subcomputations/functions appropriately** 139 | * **A bit more verbose is better than a bit shorter** 140 | * **Limit the amount of code that is "in State" to a minimum** 141 | Try to keep functions pure and use the helper functions in this package 142 | to let the work on values "in State". 143 | * **Limit the use of andThen in combination with State.state** 144 | Instead of this 145 | ```elm 146 | State.get |> andThen (\value -> state (f value)) 147 | ``` 148 | 149 | write 150 | ```elm 151 | State.map f State.get 152 | ``` 153 | 154 | # Use cases 155 | 156 | By design, most functional programs don't use state, because it's quite cumbersome to work with. 157 | It's a good idea to see whether you can do without this library. 158 | 159 | Sometimes though, there are very good reasons. This is mostly the case in traditionally imperative algorithms that use 160 | nonlocal mutable variables. There are a few other cases in standard Elm where the pattern in the (motivation)[#movation] 161 | pops up, the primary ones being working with random values and updating (child) components. This library is not made 162 | for the latter purposes, but its concepts transfer over. 163 | 164 | Finally, there is a pattern from Haskell that uses State to hold configuration information (on its own called Read) and 165 | to store logging information (on its own called Write). This pattern hasn't really found its way to Elm, 166 | and it may not need to, because Elm solves its problems differently. In any case, experiment and see what works for you. 167 | 168 | ## Caching function results 169 | 170 | Some computations are resource-intensive and should preferably only be performed once. The classical example 171 | of this is the fibonacci sequence. 172 | 173 | ```elm 174 | fib : Int -> Int 175 | fib n = 176 | case n of 177 | 0 -> 1 178 | 1 -> 1 179 | _ -> fib (n - 1) + fib (n - 2) 180 | ``` 181 | 182 | Every evaluation of `fib` (except the base cases 0 and 1) requires two more evaluations. 183 | Those evaluations each too require two more evaluations, making the time complexity of this function exponential. 184 | Furthermore, the two trees (`fib (n - 1)` and `fib (n - 2)`) overlap, doing the same calculations twice. 185 | These problems can we solved by caching already calculated values. This is where `State` comes in: 186 | 187 | ```elm 188 | fib : Int -> Int 189 | fib n = 190 | let 191 | initialState = 192 | Dict.fromList [ ( 0, 1 ), ( 1, 1 ) ] 193 | in 194 | State.finalValue initialState (fibHelper n) 195 | 196 | 197 | fibHelper : Int -> State (Dict Int Int) Int 198 | fibHelper n = 199 | let 200 | -- takes n as an argument to preserve laziness 201 | calculateStatefullFib : Int -> State (Dict Int Int) Int 202 | calculateStatefullFib n = 203 | State.map2 (+) (fibHelper (n - 1)) (fibHelper (n - 2)) 204 | 205 | addNewValue : Int -> State (Dict Int Int) Int 206 | addNewValue solution = 207 | State.modify (Dict.insert n solution) 208 | |> State.map (\_ -> solution) 209 | 210 | modifyWhenNeeded : Dict Int Int -> State (Dict Int Int) Int 211 | modifyWhenNeeded cache = 212 | case Dict.get n cache of 213 | Just cachedSolution -> 214 | state cachedSolution 215 | 216 | Nothing -> 217 | calculateStatefullFib n 218 | |> andThen addNewValue 219 | in 220 | State.get 221 | |> andThen modifyWhenNeeded 222 | ``` 223 | 224 | Notice how, with the help of choosing descriptive names, this function reads like a 225 | step-by-step description of what happens. This is what makes using `andThen` so nice. 226 | 227 | It is also possible to reuse the cache for multiple invocations of fibHelper, for example using `State.traverse`. 228 | 229 | ```elm 230 | fibs : List Int -> List Int 231 | fibs = 232 | let 233 | initialState = 234 | Dict.fromList [ ( 0, 1 ), ( 1, 1 ) ] 235 | in 236 | State.finalValue initialState << fibsHelper 237 | 238 | 239 | fibsHelper : List Int -> State (Dict Int Int) (List Int) 240 | fibsHelper = 241 | State.traverse fibHelper 242 | ``` 243 | 244 | 245 | ## Threading a Random seed 246 | 247 | When working with random values, you have to update the seed after every computation. 248 | Note how this is very similar to the general pattern in the [motivation](#motivation) section. 249 | 250 | ```elm 251 | myRandomValues = 252 | let 253 | myGenerator = Random.int 0 10 254 | 255 | (a, newSeed1) = Random.step myGenerator seed 256 | (b, newSeed2) = Random.step myGenerator newSeed1 257 | (c, newSeed3) = Random.step myGenerator newSeed2 258 | in 259 | (a, b, c) 260 | ``` 261 | 262 | The `Random.mapN` functions do exactly what `compose` described above does. I advice to 263 | use the random-specific functions when working with random values. 264 | 265 | ```elm 266 | myRandomValues = 267 | let 268 | myGenerator = Random.int 0 10 269 | in 270 | Random.map3 (,,) myGenerator myGenerator myGenerator 271 | |> (flip Random.step) seed 272 | |> Tuple.first 273 | ``` 274 | 275 | ## Recursively applying update 276 | 277 | **Don't use this library for this purpose, prefer [Fresheyeball/elm-return](http://package.elm-lang.org/packages/Fresheyeball/elm-return/latest)**. 278 | 279 | The typical signature for update with TEA is 280 | 281 | update : Msg -> Model -> (Model, Cmd Msg) 282 | 283 | When we drop the first argument (partial function application) and swap the order 284 | of the types in the tuple, we get 285 | 286 | update' : Model -> (Cmd Msg, Model) 287 | 288 | This is a function with the same type as the ones wrapped in State. 289 | The [elm-effects](http://package.elm-lang.org/packages/akbiggs/elm-effects/latest/Effects) library uses 290 | a pattern similar to this library to consecutively apply operations that generate side-effects to a value. 291 | 292 | Elm-effects is a bit less powerful than this package (technically, it's just a Writer monad, not a State monad). 293 | If you need the more powerful functions then reevaluate, use this package and maybe contribute to elm-effects. 294 | 295 | 296 | # Derivation 297 | 298 | A derivation of how the composition of functions of the form `s -> (a, s)` works. 299 | 300 | In the example from the beginning, 301 | 302 | ```elm 303 | (newValue, newState) = f state 304 | (newerValue, newerState) = g newValue newState 305 | (newererValue, newererState) = h newerValue newerState 306 | ``` 307 | 308 | `f` and `g` have the following types. 309 | 310 | ```elm 311 | f : s -> (a, s) 312 | g : a -> s -> (a, s) 313 | ``` 314 | 315 | We want to compose these two functions, which means that the `a` and `s` returned by `f` serve as 316 | an input to `g`. This can be done as follows: 317 | 318 | ```elm 319 | composed : a -> s -> (s, a) 320 | composed value state = 321 | let 322 | (newValue, newState) = f value state 323 | in 324 | g newValue newState 325 | ``` 326 | 327 | Let's generalize this a little further 328 | 329 | ```elm 330 | compose : (s -> (a, s)) 331 | -> (a -> s -> (b, s)) 332 | -> (s -> (b, s)) 333 | compose f g state = 334 | let 335 | (newValue, newState) = f state 336 | in 337 | g newValue newState 338 | ``` 339 | 340 | The essence of this library is composing multiple functions of the type `s -> (a, s)` into one giant function of the same type. 341 | All the intermediate threading (result of `f` is input to `g`) is automatically taken care of. For better error messages, 342 | the `s -> (a, s)` function is wrapped in a type. 343 | 344 | ```elm 345 | type State s a = 346 | State (s -> (a, s)) 347 | ``` 348 | 349 | Compose can then be written as 350 | ```elm 351 | compose : State s a -> (a -> State s b) -> State s b 352 | compose (State f) g = 353 | let 354 | helper : s -> (s, b) 355 | helper initialState = 356 | let (newValue, newState) = f initialState 357 | 358 | -- unwrap the newly created state 359 | (State h) = g newValue 360 | in 361 | h newState 362 | in 363 | State helper 364 | ``` 365 | 366 | Functions of the type `(a -> Wrapper b) -> Wrapper a -> Wrapper b` are called `andThen` in Elm (see 367 | [Maybe.andThen](http://package.elm-lang.org/packages/elm-lang/core/latest/Maybe#andThen) and 368 | [Random.andThen](http://package.elm-lang.org/packages/elm-lang/core/latest/Random#andThen)), but sometimes 369 | also referred to as bind or `(>>=)`. 370 | 371 | 372 | 373 | # Problems 374 | 375 | * **RangeError: Recursion limit exceeded**: Older versions of this package would often throw this error at 376 | runtime. Most of these cases are now fixed, but if you run into this error, please report it. 377 | 378 | 379 | 380 | 381 | 382 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "folkertdev/elm-state", 4 | "summary": "Threading state through computation", 5 | "license": "BSD-3-Clause", 6 | "version": "3.0.1", 7 | "exposed-modules": [ 8 | "State" 9 | ], 10 | "elm-version": "0.19.0 <= v < 0.20.0", 11 | "dependencies": { 12 | "elm/core": "1.0.0 <= v < 2.0.0" 13 | }, 14 | "test-dependencies": { 15 | "elm-explorations/test": "1.0.0 <= v < 2.0.0" 16 | } 17 | } -------------------------------------------------------------------------------- /examples/Euler14.elm: -------------------------------------------------------------------------------- 1 | module Euler14 exposing (..) 2 | 3 | import State exposing (State, andThen) 4 | import Dict exposing (Dict) 5 | import Html exposing (text) 6 | import List.Extra as List 7 | 8 | 9 | step n = 10 | if n % 2 == 0 then 11 | n // 2 12 | else 13 | 3 * n + 1 14 | 15 | 16 | increment n = 17 | step n 18 | |> generate 19 | |> State.map (\rest -> n :: rest) 20 | 21 | 22 | generate n = 23 | generateIfMissing increment n 24 | 25 | 26 | generates = 27 | State.traverse generate 28 | 29 | 30 | generateIfMissing : (comparable -> State (Dict comparable value) value) -> comparable -> State (Dict comparable value) value 31 | generateIfMissing generator key = 32 | let 33 | modifyWith : (a -> s -> s) -> a -> State s a 34 | modifyWith f value = 35 | State.modify (f value) 36 | |> State.map (\_ -> value) 37 | 38 | updateIfNeeded cache = 39 | case Dict.get key cache of 40 | Just v -> 41 | State.state v 42 | 43 | Nothing -> 44 | let 45 | result = 46 | generator key 47 | in 48 | generator key 49 | |> andThen (modifyWith (Dict.insert key)) 50 | in 51 | State.get 52 | |> andThen updateIfNeeded 53 | 54 | 55 | upper = 56 | 500000 57 | 58 | 59 | folder upper key value accum = 60 | if key <= upper then 61 | let 62 | size = 63 | List.length value 64 | in 65 | case accum of 66 | Nothing -> 67 | Just ( key, size ) 68 | 69 | Just ( keyAccum, valueAccum ) -> 70 | if size > valueAccum then 71 | Just ( key, size ) 72 | else 73 | accum 74 | else 75 | accum 76 | 77 | 78 | largest upper = 79 | generates (List.range 1 upper) 80 | |> State.finalState (Dict.fromList [ ( 1, [ 1 ] ) ]) 81 | |> Dict.foldr (folder upper) Nothing 82 | 83 | 84 | main = 85 | largest upper 86 | |> toString 87 | |> text 88 | 89 | 90 | alternative a b = 91 | case a of 92 | Just v -> 93 | Just v 94 | 95 | Nothing -> 96 | b 97 | 98 | 99 | predicate key value accum = 100 | let 101 | size = 102 | List.length value 103 | in 104 | alternative (Just size) (Maybe.map (max size) accum) 105 | -------------------------------------------------------------------------------- /examples/Euler92.elm: -------------------------------------------------------------------------------- 1 | module Euler92 exposing (..) 2 | 3 | import Html exposing (text, Html) 4 | import State exposing (..) 5 | import Dict exposing (Dict) 6 | import Array exposing (Array) 7 | 8 | 9 | digits : Int -> List Int 10 | digits value = 11 | if value == 0 then 12 | [ 0 ] 13 | else 14 | value % 10 :: digits (value // 10) 15 | 16 | 17 | square : Int -> Int 18 | square x = 19 | x * x 20 | 21 | 22 | step : Int -> Int 23 | step = 24 | List.foldl ((+) << square) 0 << digits 25 | 26 | 27 | terminator : Int -> State (Dict Int Int) Int 28 | terminator n = 29 | if n == 1 || n == 89 then 30 | state n 31 | else 32 | let 33 | updateWithValue : Int -> State (Dict Int Int) Int 34 | updateWithValue value = 35 | modify (Dict.insert n value) 36 | |> State.map (\_ -> value) 37 | 38 | updateIfNeeded : Dict Int Int -> State (Dict Int Int) Int 39 | updateIfNeeded dict = 40 | case Dict.get n dict of 41 | Just v -> 42 | state v 43 | 44 | Nothing -> 45 | terminator (step n) 46 | |> andThen updateWithValue 47 | in 48 | get 49 | |> andThen updateIfNeeded 50 | 51 | 52 | terminators : List Int -> State (Dict Int Int) (List Int) 53 | terminators = 54 | State.traverse terminator 55 | 56 | 57 | solution : Int -> Int 58 | solution n = 59 | let 60 | {- calculates the value less than n with highest step value -} 61 | upperLimit : Int 62 | upperLimit = 63 | logBase 10 (toFloat n) 64 | |> ceiling 65 | |> (\v -> (10 ^ v) - 1) 66 | 67 | {- The cache stores the numbers 68 | 1 up to upperLimit. This means that (step n) for n <= upperLimit is always in 69 | the cache. 70 | -} 71 | cache : Dict Int Int 72 | cache = 73 | List.range 1 (step upperLimit) 74 | |> terminators 75 | |> finalState Dict.empty 76 | |> Dict.insert 1 1 77 | |> Dict.insert 89 89 78 | 79 | {- many functions in a single fold 80 | Elm does not have fusion (yet) 81 | -} 82 | operation : Int -> Int -> Int 83 | operation = 84 | let 85 | filterLength = 86 | (\v accum -> 87 | if v == Just 89 then 88 | accum + 1 89 | else 90 | accum 91 | ) 92 | in 93 | filterLength << (\e -> Dict.get e cache) << step 94 | in 95 | Array.initialize n (\v -> v + 1) 96 | |> Array.foldr operation 0 97 | 98 | 99 | main = 100 | solution 99999 101 | |> toString 102 | |> text 103 | -------------------------------------------------------------------------------- /examples/Fibonacci.elm: -------------------------------------------------------------------------------- 1 | module Fibonacci exposing (..) 2 | 3 | import State exposing (state, andThen, State) 4 | import Dict exposing (Dict) 5 | import Html exposing (Html, text) 6 | 7 | 8 | fib : Int -> Int 9 | fib n = 10 | let 11 | initialState = 12 | Dict.fromList [ ( 0, 1 ), ( 1, 1 ) ] 13 | in 14 | State.finalValue initialState (fibHelper n) 15 | 16 | 17 | fibHelper : Int -> State (Dict Int Int) Int 18 | fibHelper n = 19 | let 20 | -- takes n as an argument to preserver laziness 21 | calculateStatefullFib : Int -> State (Dict Int Int) Int 22 | calculateStatefullFib n = 23 | State.map2 (+) (fibHelper (n - 1)) (fibHelper (n - 2)) 24 | 25 | addNewValue : Int -> State (Dict Int Int) Int 26 | addNewValue solution = 27 | State.modify (Dict.insert n solution) 28 | |> State.map (\_ -> solution) 29 | 30 | modifyWhenNeeded : Dict Int Int -> State (Dict Int Int) Int 31 | modifyWhenNeeded cache = 32 | case Dict.get n cache of 33 | Just cachedSolution -> 34 | state cachedSolution 35 | 36 | Nothing -> 37 | calculateStatefullFib n 38 | |> andThen addNewValue 39 | in 40 | State.get 41 | |> andThen modifyWhenNeeded 42 | 43 | 44 | fibs : List Int -> List Int 45 | fibs = 46 | let 47 | initialState = 48 | Dict.fromList [ ( 0, 1 ), ( 1, 1 ) ] 49 | in 50 | State.finalValue initialState << fibsHelper 51 | 52 | 53 | fibsHelper : List Int -> State (Dict Int Int) (List Int) 54 | fibsHelper = 55 | -- State.mapState makes sure to reuse the cache between calls to fibHelper. 56 | State.traverse fibHelper 57 | 58 | 59 | main = 60 | List.range 0 9 61 | |> fibs 62 | |> toString 63 | |> text 64 | -------------------------------------------------------------------------------- /examples/SieveOfEratosthenes.elm: -------------------------------------------------------------------------------- 1 | module SieveOfEratosthenes exposing (..) 2 | 3 | import State exposing (state, andThen, State) 4 | import Array exposing (Array) 5 | import Html exposing (text) 6 | 7 | 8 | type alias Sieve = 9 | Array (Maybe Int) 10 | 11 | 12 | type alias Config = 13 | { username : String } 14 | 15 | 16 | range : Int -> Int -> Int -> List Int 17 | range start stop step = 18 | List.range 0 ((stop - start) // step) 19 | |> List.map (\v -> v * step + start) 20 | 21 | 22 | cycle : Int -> State Sieve (Maybe Int) 23 | cycle n = 24 | let 25 | mark : Int -> State Sieve () 26 | mark n = 27 | State.modify (Array.set n Nothing) 28 | 29 | -- generate multiples of n in the range [n*n..lengthOfArray] 30 | multiplesToMark length n = 31 | range (n * n) length n 32 | 33 | getArrayLength = 34 | State.map Array.length State.get 35 | 36 | markMultiples length = 37 | -- traverse shares the Array Int beteween invocations of mark. 38 | State.traverse mark (multiplesToMark length n) 39 | 40 | setNextIndex _ = 41 | State.map (toNextIndex n) State.get 42 | in 43 | getArrayLength 44 | |> andThen markMultiples 45 | |> andThen setNextIndex 46 | 47 | 48 | recurse : Int -> (Int -> State Sieve (Maybe Int)) -> State Sieve (Maybe Int) 49 | recurse initial advance = 50 | let 51 | advanceIfPossible : Maybe Int -> State Sieve (Maybe Int) 52 | advanceIfPossible mindex = 53 | case mindex of 54 | Nothing -> 55 | state Nothing 56 | 57 | Just index -> 58 | recurse index advance 59 | in 60 | advance initial 61 | |> andThen advanceIfPossible 62 | 63 | 64 | toNextIndex : Int -> Sieve -> Maybe Int 65 | toNextIndex currentIndex sieve = 66 | let 67 | predicate e = 68 | case e of 69 | Just v -> 70 | v > currentIndex 71 | 72 | Nothing -> 73 | False 74 | in 75 | Array.filter predicate sieve 76 | |> Array.get 0 77 | |> (\m -> m |> Maybe.andThen identity) 78 | 79 | 80 | primesUpTo : Int -> Array Int 81 | primesUpTo n = 82 | -- up to, not including 83 | let 84 | initialState = 85 | Array.initialize n Just 86 | |> Array.set 0 Nothing 87 | |> Array.set 1 Nothing 88 | in 89 | recurse 2 cycle 90 | |> State.finalState initialState 91 | |> Array.foldl 92 | (\elem accum -> 93 | case elem of 94 | Nothing -> 95 | accum 96 | 97 | Just v -> 98 | Array.push v accum 99 | ) 100 | Array.empty 101 | 102 | 103 | main = 104 | primesUpTo 100 105 | |> toString 106 | |> text 107 | -------------------------------------------------------------------------------- /examples/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "elm-community/list-extra": "4.0.0 <= v < 5.0.0", 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 14 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 15 | }, 16 | "elm-version": "0.18.0 <= v < 0.19.0" 17 | } 18 | -------------------------------------------------------------------------------- /src/State.elm: -------------------------------------------------------------------------------- 1 | module State exposing 2 | ( State(..), state, embed, advance 3 | , map, map2, map3 4 | , andMap, andThen, join 5 | , get, put, modify 6 | , run, finalValue, finalState 7 | , traverse, combine, filterM, foldlM, foldrM 8 | , tailRec, tailRecM, Step(..) 9 | ) 10 | 11 | {-| This library provides ways to compose functions of the type 12 | `s -> (a, s)`. This composition threads state through a computation 13 | 14 | From time to time, you'll see a pattern like this in your code 15 | 16 | ( newValue, newState ) = 17 | f state 18 | 19 | ( newerValue, newerState ) = 20 | g newValue newState 21 | 22 | ( newererValue, newererState ) = 23 | h newerValue newerState 24 | 25 | This pattern is ugly and error-prone (because of typo's, for instance). 26 | It can be abstracted by creating a function that composes `f` and `g` ( 27 | the output of `f` is the input to `g`). 28 | 29 | f : s -> ( a, s ) 30 | 31 | g : a -> s -> ( a, s ) 32 | 33 | This library implements this composition and provides a bunch of helper functions for 34 | working with State. For a more in-depth explanation of how the implementation works, 35 | see the [derivation](https://github.com/folkertdev/elm-state#derivation). For more detailed, higher 36 | level documentation, please see the [readme](https://github.com/folkertdev/elm-state) and the [examples](https://github.com/folkertdev/elm-state/tree/master/examples) 37 | 38 | #Type and Constructors 39 | 40 | @docs State, state, embed, advance 41 | 42 | #Mapping 43 | 44 | @docs map, map2, map3 45 | 46 | #Chaining 47 | 48 | @docs andMap, andThen, join 49 | 50 | #Changing State 51 | 52 | @docs get, put, modify 53 | 54 | #Running State 55 | 56 | @docs run, finalValue, finalState 57 | 58 | #Generalized list functions 59 | 60 | @docs traverse, combine, filterM, foldlM, foldrM 61 | 62 | #Safe recursion 63 | 64 | The archetypal Haskell implementation for State will overflow the stack in strict languages like Elm. 65 | We use the fact that elm performs tail-call eliminiation to make sure the generalized list functions don't overflow the stack. 66 | 67 | To allow for full flexibility, the stack-safety primitives are exposed. Look at the source for examples. 68 | 69 | The implementation of these functions is heavily based on the 70 | [purescript MonadRec implementation](https://github.com/purescript/purescript-tailrec/blob/master/src/Control/Monad/Rec/Class.purs) 71 | 72 | @docs tailRec, tailRecM, Step 73 | 74 | #Notes for the Haskellers/curious 75 | 76 | The `State` type of this package is the `State Monad`. This wording is a little weird, it'd be better to say that 77 | `State` is a `Monad`. 78 | 79 | Monad is a concept from a branch of mathematics called category theory. In short, it is a type on which 80 | `andThen` is defined (examples in core are Random, Maybe, Result and Decoder). Many useful types are monads, 81 | and therefore being familiar with the concept can be very helpful in functional programming. 82 | 83 | Monads are also called 'computation builders': They allow for an elegant way of chaining computations with `andThen` 84 | (see the [README](https://github.com/folkertdev/elm-state#structuring-computation-with-andthen)). 85 | Elm wants to be a simple, easy to learn language, and therefore monads aren't really talked about (yet). I've tried to limit the jargon in the documentation to a minimum. 86 | If anything in the docs here or in the repository is still unclear, please open an issue [on the repo](https://github.com/folkertdev/elm-state/issues). 87 | 88 | -} 89 | 90 | -- Type and Constructors 91 | 92 | 93 | {-| Type that represents state. 94 | 95 | Note that `State` wraps a function, not a concrete value. 96 | 97 | -} 98 | type State state value 99 | = State (state -> ( value, state )) 100 | 101 | 102 | {-| Create a new State from a value of any type. 103 | -} 104 | state : value -> State state value 105 | state value = 106 | State (\s -> ( value, s )) 107 | 108 | 109 | {-| Embed a function into State. The function is applied to the state, the result 110 | will become the value. 111 | 112 | It is implemented as: 113 | 114 | embed : (a -> b) -> State a b 115 | embed f = 116 | State (\s -> ( f s, s )) 117 | 118 | This function can be extended as follows: 119 | 120 | embed2 : (a -> b -> c) -> a -> State b c 121 | embed2 f arg1 = 122 | embed (f arg1) 123 | 124 | -} 125 | embed : (s -> a) -> State s a 126 | embed f = 127 | State (\s -> ( f s, s )) 128 | 129 | 130 | {-| Wrap a function as a State. Remember that `State` is just a wrapper around 131 | a function of type `s -> ( a, s )`. 132 | -} 133 | advance : (s -> ( a, s )) -> State s a 134 | advance f = 135 | State f 136 | 137 | 138 | 139 | -- Mapping 140 | 141 | 142 | {-| Apply a function to the value that the state holds 143 | -} 144 | map : (a -> b) -> State s a -> State s b 145 | map f (State step) = 146 | State <| 147 | \currentState -> 148 | let 149 | ( value, newState ) = 150 | step currentState 151 | in 152 | ( f value, newState ) 153 | 154 | 155 | {-| Apply a function to the value of two states. The newest state will be kept 156 | -} 157 | map2 : (a -> b -> c) -> State s a -> State s b -> State s c 158 | map2 f (State step1) (State step2) = 159 | State <| 160 | \currentState -> 161 | let 162 | ( value1, newState ) = 163 | step1 currentState 164 | 165 | ( value2, newerState ) = 166 | step2 newState 167 | in 168 | ( f value1 value2, newerState ) 169 | 170 | 171 | {-| Apply a function to the value of three states. The newest state will be kept 172 | 173 | The definition of map3 is in terms of andMap, which can be used to create 174 | map4, map5 ect. 175 | 176 | map3 : 177 | (a -> b -> c -> d) 178 | -> State s a 179 | -> State s b 180 | -> State s c 181 | -> State s d 182 | map3 f step1 step2 step3 = 183 | map f step1 184 | |> andMap step2 185 | |> andMap step3 186 | 187 | -} 188 | map3 : (a -> b -> c -> d) -> State s a -> State s b -> State s c -> State s d 189 | map3 f step1 step2 step3 = 190 | map f step1 191 | |> andMap step2 192 | |> andMap step3 193 | 194 | 195 | 196 | -- Chaining 197 | 198 | 199 | {-| Apply a function wrapped in a state to a value wrapped in a state. 200 | This is very useful for applying stateful arguments one by one. 201 | 202 | The use of `andMap` can be substituted by using mapN. The following 203 | expressions are equivalent. 204 | 205 | map f arg1 |> andMap arg2 == State.map2 f arg1 arg2 206 | 207 | In general, using the `mapN` functions is preferable. The `mapN` functions can 208 | be defined up to an arbitrary `n` using `andMap`. 209 | 210 | State.mapN f arg1 arg2 ... argN 211 | == State.map f arg1 212 | |> andMap arg2 213 | ... 214 | |> andMap argN 215 | 216 | -} 217 | andMap : State s a -> State s (a -> b) -> State s b 218 | andMap = 219 | \b a -> map2 (<|) a b 220 | 221 | 222 | {-| Chain two operations with state. 223 | 224 | The [readme](https://github.com/folkertdev/elm-state) has a section on [structuring computation 225 | with `andThen`](https://github.com/folkertdev/elm-state#structuring-computation-with-andthen). 226 | 227 | -} 228 | andThen : (a -> State s b) -> State s a -> State s b 229 | andThen f (State h) = 230 | State <| 231 | \s -> 232 | let 233 | ( a, newState ) = 234 | h s 235 | 236 | (State g) = 237 | f a 238 | in 239 | g newState 240 | 241 | 242 | {-| Discard a level of state. 243 | -} 244 | join : State s (State s a) -> State s a 245 | join (State f) = 246 | -- andThen identity (State f) 247 | State <| 248 | \s -> 249 | let 250 | ( State g, newState ) = 251 | f s 252 | in 253 | g newState 254 | 255 | 256 | 257 | -- Changing the state 258 | 259 | 260 | {-| Get the current state. Typically the state is 261 | modified somehow and then put back with put. 262 | -} 263 | get : State s s 264 | get = 265 | State (\s -> ( s, s )) 266 | 267 | 268 | {-| Replace the current state with a new one. 269 | -} 270 | put : s -> State s () 271 | put x = 272 | State (\_ -> ( (), x )) 273 | 274 | 275 | {-| Modify the state. This is a combination of set and put 276 | 277 | An example using `State.get` and `State.modify`: 278 | 279 | terminator : Int -> State (Dict Int Int) Int 280 | terminator n = 281 | if n == 1 || n == 89 then 282 | state n 283 | 284 | else 285 | let 286 | updateWithValue : Int -> State (Dict Int Int) Int 287 | updateWithValue value = 288 | modify (Dict.insert n value) 289 | |> State.map (\_ -> value) 290 | 291 | updateIfNeeded : 292 | Dict Int Int 293 | -> State (Dict Int Int) Int 294 | updateIfNeeded dict = 295 | case Dict.get n dict of 296 | Just v -> 297 | state v 298 | 299 | Nothing -> 300 | terminator (step n) 301 | |> andThen updateWithValue 302 | in 303 | get 304 | |> andThen updateIfNeeded 305 | 306 | -} 307 | modify : (s -> s) -> State s () 308 | modify f = 309 | State (\s -> ( (), f s )) 310 | 311 | 312 | {-| Thread the state through a computation, 313 | and return both the final state and the computed value 314 | 315 | Note for Haskellers: the argument order is swapped. This is more 316 | natural in elm because code is often structured left to right using `(|>)`. 317 | 318 | -} 319 | run : s -> State s a -> ( a, s ) 320 | run initialState (State s) = 321 | s initialState 322 | 323 | 324 | {-| Thread the state through a computation, 325 | and return only the computed value 326 | 327 | 328 | fibs : List Int -> List Int 329 | fibs = 330 | let 331 | initialState = 332 | Dict.fromList [ ( 0, 1 ), ( 1, 1 ) ] 333 | in 334 | State.finalValue initialState << fibsHelper 335 | 336 | 337 | -- fibsHelper : List Int -> State (Dict Int Int) (List Int) 338 | 339 | See [Fibonacci.elm](https://github.com/folkertdev/elm-state/blob/master/examples/Fibonacci.elm) for the full example. 340 | 341 | -} 342 | finalValue : s -> State s a -> a 343 | finalValue initialState = 344 | Tuple.first << run initialState 345 | 346 | 347 | {-| Thread the state through a computation, 348 | and return only the final state 349 | 350 | primesUpTo : Int -> Array Int 351 | primesUpTo n = 352 | let 353 | initialState = 354 | Array.repeat n True 355 | |> Array.set 0 False 356 | |> Array.set 1 False 357 | in 358 | recurse 2 cycle 359 | |> State.finalState initialState 360 | |> Array.indexedMap (\a b -> ( a, b )) 361 | |> Array.filter (\( i, v ) -> v == True) 362 | |> Array.map fst 363 | 364 | See [SieveOfErastosthenes.elm](https://github.com/folkertdev/elm-state/blob/master/examples/SieveOfEratosthenes.elm) for the full example. 365 | 366 | -} 367 | finalState : s -> State s a -> s 368 | finalState initialState = 369 | Tuple.second << run initialState 370 | 371 | 372 | 373 | -- Generalized list functions 374 | 375 | 376 | {-| Generalize `List.map` to work with `State`. 377 | 378 | When you have a function the works on a single element, 379 | 380 | mark : Int -> State (Array Bool) () 381 | mark index = 382 | State.modify (Array.set index False) 383 | 384 | traverse can be used to let it work on a list of elements, 385 | taking care of threading the state through. 386 | 387 | markMany : List Int -> State (Array Bool) (List ()) 388 | markMany = 389 | State.traverse mark 390 | 391 | This function is also called `mapM`. 392 | 393 | -} 394 | traverse : (a -> State s b) -> List a -> State s (List b) 395 | traverse f = 396 | map List.reverse << foldlM (\accum elem -> map2 (::) (f elem) (state accum)) [] 397 | 398 | 399 | {-| Combine a list of `State`s into one by composition. 400 | The resulting value is a list of the results of subcomputations. 401 | -} 402 | combine : List (State s a) -> State s (List a) 403 | combine = 404 | traverse identity 405 | 406 | 407 | {-| Generalize `List.filter` to work on `State`. Composes only the states that satisfy the predicate. 408 | 409 | -- keep only items that occur at least once 410 | [ 1, 2, 3, 4, 4, 5, 5, 1 ] 411 | |> State.filter (\element -> State.advance (\cache -> (List.member element cache, element :: cache))) 412 | |> State.run [] 413 | --> ([4,5,1], [1,5,5,4,4,3,2,1]) 414 | 415 | -} 416 | filterM : (a -> State s Bool) -> List a -> State s (List a) 417 | filterM predicate = 418 | let 419 | folder elem accum = 420 | let 421 | keepIfTrue verdict = 422 | if verdict then 423 | elem :: accum 424 | 425 | else 426 | accum 427 | in 428 | predicate elem 429 | |> map keepIfTrue 430 | in 431 | map List.reverse << foldlM (\b a -> folder a b) [] 432 | 433 | 434 | {-| Compose a list of updated states into one from the left. Also called `foldM`. 435 | -} 436 | foldlM : (b -> a -> State s b) -> b -> List a -> State s b 437 | foldlM f = 438 | let 439 | step accum elements = 440 | case elements of 441 | [] -> 442 | state (Done accum) 443 | 444 | x :: xs -> 445 | f accum x 446 | |> map (\a_ -> Loop ( a_, xs )) 447 | in 448 | tailRecM2 step 449 | 450 | 451 | {-| Compose a list of updated states into one from the right 452 | -} 453 | foldrM : (a -> b -> State s b) -> b -> List a -> State s b 454 | foldrM f initialValue xs = 455 | foldlM (\b a -> f a b) initialValue (List.reverse xs) 456 | 457 | 458 | {-| Perform an action n times, gathering the results 459 | -} 460 | replicateM : Int -> State s a -> State s (List a) 461 | replicateM total s = 462 | let 463 | go ( n, xs ) = 464 | if n < 1 then 465 | state (Done xs) 466 | 467 | else 468 | map (\x -> Loop ( n - 1, x :: xs )) s 469 | in 470 | tailRecM go ( total, [] ) 471 | 472 | 473 | zipWithM : (a -> b -> State s c) -> List a -> List b -> State s (List c) 474 | zipWithM f ps qs = 475 | combine (List.map2 f ps qs) 476 | 477 | 478 | mapAndUnzipM : (a -> State s ( b, c )) -> List a -> State s ( List b, List c ) 479 | mapAndUnzipM f xs = 480 | map List.unzip (traverse f xs) 481 | 482 | 483 | 484 | -- Tail Recursion - based on purescript https://github.com/purescript/purescript-tailrec/blob/master/src/Control/Monad/Rec/Class.purs 485 | 486 | 487 | {-| The result of a compuation: either `Loop` containing the updated accumulator, 488 | or `Done` containing the final result of the computation. 489 | -} 490 | type Step a b 491 | = Loop a 492 | | Done b 493 | 494 | 495 | {-| Create a pure tail-recursive function of one argument 496 | 497 | pow : number -> Int -> number 498 | pow n p = 499 | let 500 | go { accum, power } = 501 | if power == 0 then 502 | Done accum 503 | 504 | else 505 | Loop 506 | { accum = accum * n 507 | , power = power - 1 508 | } 509 | in 510 | tailRec go { accum = 1, power = p } 511 | 512 | -} 513 | tailRec : (a -> Step a b) -> a -> b 514 | tailRec f = 515 | let 516 | go step = 517 | case step of 518 | Loop a -> 519 | go (f a) 520 | 521 | Done b -> 522 | b 523 | in 524 | go << f 525 | 526 | 527 | tailRecM2 : (a -> c -> State s (Step ( a, c ) b)) -> a -> c -> State s b 528 | tailRecM2 f a b = 529 | tailRecM (\( x, y ) -> f x y) ( a, b ) 530 | 531 | 532 | {-| The `tailRecM` function takes a step function and applies it recursively until 533 | a pure value of type `b` is found. Because of tail recursion, this function runs in constant stack-space. 534 | 535 | {-| Perform an action n times, gathering the results 536 | -} 537 | replicateM : Int -> State s a -> State s (List a) 538 | replicateM n s = 539 | let 540 | go ( n, xs ) = 541 | if n < 1 then 542 | state (Done xs) 543 | 544 | else 545 | map (\x -> Loop ( n - 1, x :: xs )) s 546 | in 547 | tailRecM go ( n, [] ) 548 | 549 | -} 550 | tailRecM : (a -> State s (Step a b)) -> a -> State s b 551 | tailRecM f a = 552 | let 553 | helper : ( Step a b, c ) -> Step ( a, c ) ( b, c ) 554 | helper ( m, s1 ) = 555 | case m of 556 | Loop x -> 557 | Loop ( x, s1 ) 558 | 559 | Done y -> 560 | Done ( y, s1 ) 561 | 562 | step : ( a, s ) -> Step ( a, s ) ( b, s ) 563 | step ( value, s ) = 564 | case f value of 565 | State st -> 566 | helper (st s) 567 | in 568 | State <| \s -> tailRec step ( a, s ) 569 | -------------------------------------------------------------------------------- /tests/Tests.elm: -------------------------------------------------------------------------------- 1 | module Tests exposing (evaluate, folds, traverse) 2 | 3 | import Expect 4 | import Fuzz exposing (int, list, string, tuple) 5 | import State exposing (State, andThen, state) 6 | import String 7 | import Test exposing (..) 8 | 9 | 10 | flip f a b = 11 | f b a 12 | 13 | 14 | evaluate a b = 15 | Expect.equal (State.run () a) (State.run () b) 16 | 17 | 18 | stateTests = 19 | describe "Sample Test Suite" 20 | [ describe "Unit test examples" 21 | [ test "Join = AndThen identity" <| 22 | \() -> 23 | let 24 | a = 25 | state (state 2) 26 | |> andThen identity 27 | 28 | b = 29 | state (state 2) 30 | |> State.join 31 | in 32 | evaluate a b 33 | , test "map f x = x |> andThen (state << f) " <| 34 | \() -> 35 | let 36 | a = 37 | State.map (\x -> x + 2) (state 2) 38 | 39 | b = 40 | state 2 41 | |> andThen (\x -> state (x + 2)) 42 | in 43 | evaluate a b 44 | , test "filterM documentation example" <| 45 | \() -> 46 | let 47 | like : String -> String 48 | like subject = 49 | "I like " ++ subject ++ "s" 50 | 51 | rodents = 52 | [ "hamster", "rabbit", "guinea pig" ] 53 | 54 | result = 55 | State.filterM (State.embed << List.member) rodents 56 | |> State.map (List.map like) 57 | |> State.map (String.join " and ") 58 | |> State.run [ "cat", "dog", "hamster" ] 59 | in 60 | Expect.equal ( "I like hamsters", [ "cat", "dog", "hamster" ] ) result 61 | , test "get documentation example" <| 62 | \() -> 63 | State.map2 (+) State.get State.get 64 | |> State.run 42 65 | |> Expect.equal ( 84, 42 ) 66 | , test "set documentation example" <| 67 | \() -> 68 | State.put 5 69 | |> State.run 3 70 | |> Expect.equal ( (), 5 ) 71 | , test "modify documentation example" <| 72 | \() -> 73 | State.modify (\v -> v + 1) 74 | |> State.map (\_ -> "finished") 75 | |> State.run 42 76 | |> Expect.equal ( "finished", 43 ) 77 | ] 78 | , describe "Fuzz test examples, using randomly generated input" 79 | [ fuzz (list int) "Lists always have positive length" <| 80 | \aList -> 81 | List.length aList |> Expect.atLeast 0 82 | ] 83 | , describe "Monad Laws" 84 | [ fuzz int "Left identity" <| 85 | let 86 | f x = 87 | state (x + x) 88 | in 89 | \value -> evaluate (state value |> andThen f) (f value) 90 | , fuzz int "Right identity" <| 91 | let 92 | f x = 93 | state (x + x) 94 | in 95 | \value -> evaluate (state value |> andThen state) (state value) 96 | , fuzz int "Associativity" <| 97 | \value -> 98 | let 99 | f x = 100 | state (x + x) 101 | 102 | g x = 103 | state (x * x) 104 | 105 | m = 106 | state 2 107 | in 108 | evaluate (andThen g (andThen f m)) (andThen (\x -> f x |> andThen g) m) 109 | ] 110 | ] 111 | 112 | 113 | traverse = 114 | let 115 | advancer x = 116 | State.advance (\state -> ( state, state + x )) 117 | in 118 | describe "traverse" 119 | [ test "expect traverse leaves the list the same" <| 120 | \() -> 121 | let 122 | list = 123 | List.range 0 10 124 | in 125 | list 126 | |> State.traverse advancer 127 | |> State.finalValue 0 128 | |> Expect.equal [ 0, 0, 1, 3, 6, 10, 15, 21, 28, 36, 45 ] 129 | , test "filterM works" <| 130 | \() -> 131 | [ "a", "b", "c", "d", "a", "c", "b", "d" ] 132 | |> State.filterM (\x -> State.advance (\s -> ( List.member x s, x :: s ))) 133 | |> State.finalValue [] 134 | |> Expect.equal [ "a", "c", "b", "d" ] 135 | , test "filterM documentation example" <| 136 | \_ -> 137 | [ 1, 2, 3, 4, 4, 5, 5, 1 ] 138 | |> State.filterM (\element -> State.advance (\cache -> ( List.member element cache, element :: cache ))) 139 | |> State.run [] 140 | |> Expect.equal ( [ 4, 5, 1 ], [ 1, 5, 5, 4, 4, 3, 2, 1 ] ) 141 | , test "foldrM doesn't blow the stack" <| 142 | \() -> 143 | List.range 0 100000 144 | |> State.foldrM (\a b -> state (a + b)) 0 145 | |> State.finalValue () 146 | |> Expect.equal (List.foldr (+) 0 <| List.range 0 100000) 147 | , test "filterM doesn't blow the stack" <| 148 | \() -> 149 | List.range 0 100000 150 | |> State.filterM (\x -> state (x > -1)) 151 | |> State.finalValue () 152 | |> Expect.equal (List.filter (\v -> v > -1) <| List.range 0 100000) 153 | ] 154 | 155 | 156 | folds = 157 | let 158 | example = 159 | [ "foo", "bar", "baz" ] 160 | in 161 | describe "folds" 162 | [ test "foldlM behaves the same as foldl" <| 163 | \_ -> 164 | State.foldlM (flip (\x y -> State.state (x ++ y))) "" example 165 | |> State.finalValue () 166 | |> Expect.equal (List.foldl (++) "" example) 167 | , test "foldrM behaves the same as foldr" <| 168 | \_ -> 169 | State.foldrM (\x y -> State.advance (\s -> ( x ++ s, x ++ y ))) "" example 170 | |> State.finalState "" 171 | |> Expect.equal (List.foldr (++) "" example) 172 | ] 173 | -------------------------------------------------------------------------------- /tests/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Test Suites", 4 | "repository": "https://github.com/folkertdev/elm-state.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "../src", 8 | "." 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "elm-community/elm-test": "4.0.0 <= v < 5.0.0", 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0" 14 | }, 15 | "elm-version": "0.18.0 <= v < 0.19.0" 16 | } 17 | --------------------------------------------------------------------------------