├── .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 |
--------------------------------------------------------------------------------