folkertdev / elm-state / State

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 newerState

This 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


type State state value
    = State (state -> ( value, state ))

Type that represents state.

Note that State wraps a function, not a concrete value.

state : value -> State state value

Create a new State from a value of any type.

embed : (s -> a) -> State s a

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)

map : (a -> b) -> State s a -> State s b

Apply a function to the value that the state holds

map2 : (a -> b -> c) -> State s a -> State s b -> State s c

Apply a function to the value of two states. The newest state will be kept

andMap : State s a -> State s (a -> b) -> State s b

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 arg2

In 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 argN

andThen : (a -> State s b) -> State s a -> State s b

Chain two operations with state.

The readme has a section on structuring computation with andThen.

get : State s s

Get the current state. Typically the state is modified somehow and then put back with put.

put : s -> State s ()

Replace the current state with a new one.

run : s -> State s a -> ( a, s )

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 (|>).

finalValue : s -> State s a -> a

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.

traverse : (a -> State s b) -> List a -> State s (List b)

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 mark

This function is also called mapM.

combine : List (State s a) -> State s (List a)

Combine a list of States into one by composition. The resulting value is a list of the results of subcomputations.

filterM : (a -> State s Basics.Bool) -> List a -> State s (List a)

Generalize List.filter to work on State. Composes only the states that satisfy the predicate.

-- keep only items that occur at least once
[ 1, 2, 3, 4, 4, 5, 5, 1 ]
    |> State.filter (\element -> State.advance (\cache -> (List.member element cache, element :: cache)))
    |> State.run []
    --> ([4,5,1], [1,5,5,4,4,3,2,1])

foldlM : (b -> a -> State s b) -> b -> List a -> State s b

Compose a list of updated states into one from the left. Also called foldM.

tailRec : (a -> Step a b) -> a -> b

Create a pure tail-recursive function of one argument

pow : number -> Int -> number
pow n p =
    let
        go { accum, power } =
            if power == 0 then
                Done accum

            else
                Loop
                    { accum = accum * n
                    , power = power - 1
                    }
    in
    tailRec go { accum = 1, power = p }

tailRecM : (a -> State s (Step a b)) -> a -> State s b

The tailRecM function takes a step function and applies it recursively until a pure value of type b is found. Because of tail recursion, this function runs in constant stack-space.

{-| Perform an action n times, gathering the results
-}
replicateM : Int -> State s a -> State s (List a)
replicateM n s =
    let
        go ( n, xs ) =
            if n < 1 then
                state (Done xs)

            else
                map (\x -> Loop ( n - 1, x :: xs )) s
    in
    tailRecM go ( n, [] )