jsuder-xx/elm-stax/1.0.1

NOTE: the latest version is 2.0.0

Stax.RWS

A Reader Writer State monad stack. This stack cannot fail. Useful for pure computations that need configuration, logging, and mutable state—such as interpreters.

  • The "Reader" part lets you thread configuration through computations. It is like functional dependency injection with the configuration applied LAST.
  • The "Writer" part lets you thread logging through computations. You tell to log.
  • The "State" part lets you thread state through computations.

Use RWS when you never need to fail; use Stax.RWSE when you need fail-fast error handling.

Example

Declare small stacks at the top level, then compose them:

type alias Config =
    { step : Int }


type alias State =
    { total : Int }


addStep : RWS Config String State ()
addStep =
    ask
        |> andThen (\cfg -> modify (\s -> { s | total = s.total + cfg.step }))
        |> andThen_ (tell [ "addStep" ])


double : RWS Config String State ()
double =
    modify (\s -> { s | total = s.total * 2 })
        |> andThen_ (tell [ "double" ])


computation : RWS Config String State ()
computation =
    addStep
        |> andThen_ double
        |> andThen_ addStep


-- runWith { step = 1 } { total = 0 } computation
-- => ( { total = 3 }, [ "addStep", "double", "addStep" ], () )

Unneeded Facets

Just use unit () if you don't need a facet of this. For example,

  • If you declared type alias MyStack a = RWS () () () a that would basically do nothing.
  • You could still
    • get, modify, and put the state but it would be unit so uninteresting.
    • ask for the configuration but it would be unit so uninteresting.
    • tell to log but the result would be a list of units which is uninteresting.

Functionality By Category

Type, Construct, Run

type RWS config log state a

A Reader Writer State monad stack. This stack cannot fail. Useful for pure computations that need configuration, logging, and mutable state—such as interpreters.

  • The "Reader" part lets you thread configuration through computations. It is like functional dependency injection with the configuration applied LAST.
  • The "Writer" part lets you thread logging through computations. You tell to log.
  • The "State" part lets you thread state through computations.

Use RWS when you never need to fail; use Stax.RWSE when you need fail-fast error handling.

rws : (config -> state -> ( state, List log, a )) -> RWS config log state a

Construct a stack from a raw function. Low-level; prefer pure, andThen, and the other combinators.

run : RWS config log state a -> config -> state -> ( state, List log, a )

Execute the stack with config and state. Returns (finalState, logs, result).

runWith : config -> state -> RWS config log state a -> ( state, List log, a )

Same as run but takes config and state first. Pipeline-friendly: runWith config state myStack.

pure : a -> RWS config log state a

Wrap a value in the stack.

doNothing : RWS config log state ()

No-op. Useful with andThen_ when you need a unit-producing computation.

Functor/Applicative/Monad

RWS config log state a
-> RWS config log state (a -> b)
-> RWS config log state b

Applicative apply. pure f |> andMap a |> andMap b lifts a two-argument function over the stack.

(a -> RWS config log state b)
-> RWS config log state a
-> RWS config log state b

Monadic bind. Chain computations; each step receives the result of the previous.

RWS config log state b
-> RWS config log state ()
-> RWS config log state b

Chain when the first computation produces (). Cleaner than andThen (\_ -> second).

pure () |> andThen_ (tell [ "Hello" ])
map : (a -> b) -> RWS config log state a -> RWS config log state b

Transform the result of a stack.

(( a, config, state ) -> b)
-> RWS config log state a
-> RWS config log state b

Map the result of a stack with knowledge of the config and state.

(a -> b -> c)
-> RWS config log state a
-> RWS config log state b
-> RWS config log state c

Lift a two-argument function over the stack. Equivalent to pure f |> andMap a |> andMap b.

(a -> b -> c -> d)
-> RWS config log state a
-> RWS config log state b
-> RWS config log state c
-> RWS config log state d

Lift a three-argument function over the stack.

(a -> b -> c -> d -> e)
-> RWS config log state a
-> RWS config log state b
-> RWS config log state c
-> RWS config log state d
-> RWS config log state e

Shorthand for andMapping.

map4 f a b c d


-- is equivalent to
pure f |> andMap a |> andMap b |> andMap c |> andMap d
(a -> b -> c -> d -> e -> f)
-> RWS config log state a
-> RWS config log state b
-> RWS config log state c
-> RWS config log state d
-> RWS config log state e
-> RWS config log state f

Shorthand for andMapping.

map5 f a b c d e


-- is equivalent to
pure f |> andMap a |> andMap b |> andMap c |> andMap d |> andMap e
when : Bool -> RWS config log state () -> RWS config log state ()

Execute a computation only when the condition is true; otherwise do nothing.

when (count > 0) (modify (\s -> { s | count = s.count - 1 }))
whenJust : Maybe a -> (a -> RWS config log state ()) -> RWS config log state ()

Execute a computation only when the Maybe has a value.

whenJust (Just 42) (\n -> tell [ "got " ++ String.fromInt n ])
Result e a
-> (a -> RWS config log state ())
-> RWS config log state ()

Execute a computation only when the Result is Ok.

whenOk (Ok 42) (\n -> tell [ "success: " ++ String.fromInt n ])

Foldable/Traversable

combine : List (RWS config log state a) -> RWS config log state (List a)

Run a list of stacks and collect results. Also known as sequence. Equivalent to combineMap identity.

(a -> RWS config log state b)
-> List a
-> RWS config log state (List b)

Map each element through the stack and collect results. Also known as traverse.

for_ : List a -> (a -> RWS config log state ()) -> RWS config log state ()

Like a for loop: run a computation for each element, discarding results.

for_ [ 1, 2, 3 ] (\n -> tell [ "processing " ++ String.fromInt n ])
(localState -> a -> RWS config log state localState)
-> localState
-> List a
-> RWS config log state localState

Iterate over a list with an accumulator. Like List.foldl but the step runs in the stack (config, state, logging).

foldM (\acc x -> pure (acc + x)) 0 [ 1, 2, 3 ]

Configuration (Reader)

ask : RWS config log state config

Read the configuration as the value. See andModify, andTell, and mapWith for alternatives that avoid separate ask/get.

asks : (config -> a) -> RWS config log state a

Read a projection of the configuration. Equivalent to ask |> map f.

Logging (Writer)

tell : List log -> RWS config log state ()

Append to the log. All tell output is collected and returned at the end of run.

(( a, config, state ) -> List log)
-> RWS config log state a
-> RWS config log state a

This convenience function allows you to log based on the result of a prior computation, the configuration, and the current state and then return the original value. So it acts like a kind of sink.

tellSingle : log -> RWS config log state ()

Convenience function to write a single thing.

State

get : RWS config log state state

Read the current state into the value. Prefer modify or andModify when you only need to update.

gets : (state -> a) -> RWS config log state a

Read a projection of the state. Equivalent to get |> map f.

modify : (state -> state) -> RWS config log state ()

Update the state by applying a function. See andModify for a version that also has the prior result and config.

put : state -> RWS config log state ()

Replace the state entirely.

(( state, a, config ) -> state)
-> RWS config log state a
-> RWS config log state a

This convenience function supports modifying the state based on the state, the result of a prior computation, and the configuration.

Miscellaneous

askGet : RWS config log state ( config, state )

Want to read the configuration and the state all in one fell swoop? This is it.

Transformations

Lens parentState childState
-> RWS config log childState a
-> RWS config log parentState a

Lift a computation that works on a child state to work on a parent state. Accepts record-shaped optics compatible with Monocle; Elm's structural typing means any record with get : parent -> child and set : child -> parent -> parent works.

lens = { get = .count, set = \v s -> { s | count = v } }
liftStateLens lens (modify ((+) 1))
RWS config log parentState a
-> Optional parentState childCaseState
-> RWS config log childCaseState a
-> RWS config log parentState a

Lift a computation to work on a parent state when the child may be missing (Optional/AffineTraversal). The first argument runs if the child is missing; it uses the parent state.

RWS config log customType a
-> Prism customType constructorInfo
-> RWS config log constructorInfo a
-> RWS config log customType a

Lift a computation to work on a custom type when focusing on one constructor via a Prism. The first argument runs if the constructor does not match.

ignore : RWS config log state a -> RWS config log state ()

Throw away a value.