This library provides ways to compose functions of the type
s -> (a, s). This composition threads state through a computation
From time to time, you'll see a pattern like this in your code
(newValue, newState) = f state
(newerValue, newerState) = g newValue newState
(newererValue, newererState) = h newerValue newerStateThis pattern is ugly and error-prone (because of typo's, for instance).
It can be abstracted by creating a function that composes f and g (
the output of f is the input to g).
f : s -> (a, s)
g : a -> s -> (a, s)This library implements this composition and provides a bunch of helper functions for working with State. For a more in-depth explanation of how the implementation works, see the derivation. For more detailed, higher level documentation, please see the readme and the examples
#Type and Constructors
Create a new State from a value of any type.
Embed a function into State. The function is applied to the state, the result will become the value.
It is implemented as:
embed : (a -> b) -> State a b
embed f =
State (\s -> ( f s, s ))This function can be extended as follows:
embed2 : (a -> b -> c) -> a -> State b c
embed2 f arg1 =
embed (f arg1)Wrap a function as a State. Remember that State is just a wrapper around
a function of type s -> ( a, s ).
#Mapping
Apply a function to the value that the state holds
Apply a function to the value of two states. The newest state will be kept
Apply a function to the value of three states. The newest state will be kept
The definition of map3 is in terms of andMap, which can be used to create map4, map5 ect.
map3
: (a -> b -> c -> d)
-> State s a
-> State s b
-> State s c
-> State s d
map3 f step1 step2 step3 =
map f step1
|> andMap step2
|> andMap step3#Chaining
Apply a function wrapped in a state to a value wrapped in a state. This is very useful for applying stateful arguments one by one.
The use of andMap can be substituted by using mapN. The following
expressions are equivalent.
map f arg1 |> andMap arg2 == State.map2 f arg1 arg2In general, using the mapN functions is preferable. The mapN functions can
be defined up to an arbitrary n using andMap.
State.mapN f arg1 arg2 ... argN
== State.map f arg1
|> andMap arg2
...
|> andMap argNChain two operations with state.
The readme has a section on structuring computation
with andThen.
Discard a level of state.
#Changing State
Get the current state. Typically the state is modified somehow and then put back with put.
Replace the current state with a new one.
Modify the state. This is a combination of set and put
An example using State.get and State.modify:
terminator : Int -> State (Dict Int Int) Int
terminator n =
if n == 1 || n == 89 then
state n
else
let
updateWithValue : Int -> State (Dict Int Int) Int
updateWithValue value =
modify (Dict.insert n value)
|> State.map (\_ -> value)
updateIfNeeded
: Dict Int Int
-> State (Dict Int Int) Int
updateIfNeeded dict =
case Dict.get n dict of
Just v ->
state v
Nothing ->
terminator (step n)
|> andThen updateWithValue
in
get
|> andThen updateIfNeeded#Running State
Thread the state through a computation, and return both the final state and the computed value
Note for Haskellers: the argument order is swapped. This is more
natural in elm because code is often structured left to right using (|>).
Thread the state through a computation, and return only the computed value
fibs : List Int -> List Int
fibs =
let
initialState =
Dict.fromList [ ( 0, 1 ), ( 1, 1 ) ]
in
State.finalValue initialState << fibsHelper
-- fibsHelper : List Int -> State (Dict Int Int) (List Int)See Fibonacci.elm for the full example.
Thread the state through a computation, and return only the final state
primesUpTo : Int -> Array Int
primesUpTo n =
let
initialState =
Array.repeat n True
|> Array.set 0 False
|> Array.set 1 False
in
recurse 2 cycle
|> State.finalState initialState
|> Array.indexedMap (,)
|> Array.filter (\( i, v ) -> v == True)
|> Array.map fstSee SieveOfErastosthenes.elm for the full example.
#Generalized list functions
Generalize List.map to work with State.
When you have a function the works on a single element,
mark : Int -> State (Array Bool) ()
mark index =
State.modify (Array.set index False)traverse can be used to let it work on a list of elements, taking care of threading the state through.
markMany : List Int -> State (Array Bool) (List ())
markMany = State.traverse markThis function is also called mapM.
Combine a list of State's into one by composition. The resulting value is a list of the results of subcomputations.
Generalize List.filter to work on State. Composes only the states that satisfy the predicate.
like : String -> String
like subject =
"I like " ++ subject ++ "s"
rodents =
[ "hamster", "rabbit", "guinea pig" ]
result =
filterM (State.embed << List.member) rodents
|> State.map (List.map like)
|> State.map (String.join " and ")
|> State.run [ "cat", "dog", "hamster" ]
-- == ("I like hamsters", ["cat", "dog", "hamster"])Compose a list of updated states into one from the left. Also called foldM.
Compose a list of updated states into one from the right
#Safe recursion The archetypal Haskell implementation for State will overflow the stack in strict languages like Elm. The generalized list functions above use the function below to ensure tail-recursion. Because Elm has tail-call elimination, the evaluation of State is written as a loop and runs in constant space.
Make a State-function tail-recursive
The core idea is to peel layers of the computation, instead of doing the whole thing at once. When the peeling is done right, the compiler will optimize using tail-call elimination
Look at this package's source for usage examples
#Notes for the Haskellers/curious
The State type of this package is the State Monad. This wording is a little weird, it'd be better to say that
State is a Monad.
Monad is a concept from a branch of mathematics called category theory. In short, it is a type on which
andThen is defined (examples in core are Random, Maybe, Result and Decoder). Many useful types are monads,
and therefore being familiar with the concept can be very helpful in functional programming.
Monads are also called 'computation builders': They allow for an elegant way of chaining computations with andThen
(see the README).
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.
If anything in the docs here or in the repository is still unclear, please open an issue on the repo.
Type that represents state.
Note that
Statewraps a function, not a concrete value.