jsuder-xx/elm-stax/1.0.1

NOTE: the latest version is 2.0.0

Stax.RWSE

A Reader Writer State Except monad stack. Useful when pure computations can fail—interpreters, compilers.

  • The "Reader" part lets you thread configuration through computations (e.g., compiler flags). It is like functional dependency injection with the configuration applied LAST.
  • The "Writer" part lets you thread logging through computations. You tell to log; messages are read at the end.
  • The "State" part lets you thread state through computations (e.g., variable aliases when unifying, symbols encountered).
  • The "Except" part gives you fail-fast semantics. It is based on Result but the API gives you throw, catch, and try which looks like imperative programming.

Use RWSE over RWS when you need fail-fast error handling.

Example

Declare small stacks at the top level, then compose them. Use throw and catch when a step can fail:

type Error
    = ExceededMax Int


type alias Config =
    { step : Int, maxTotal : Int }


type alias State =
    { total : Int }


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


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


checkMax : RWSE Config String State Error ()
checkMax =
    askGet
        |> andThen
            (\( cfg, s ) ->
                if s.total > cfg.maxTotal then
                    throw (ExceededMax s.total)


                else
                    tell [ "within limit " ++ String.fromInt s.total ]
            )


computation : RWSE Config String State Error ()
computation =
    addStep
        |> andThen_ double
        |> andThen_ checkMax
        |> andThen_ addStep


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

Unneeded Facets

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

  • If you declared type alias MyStack a = RWSE () () () () a that would basically just be Result.
  • And 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 RWSE config log state error a

A Reader Writer State Except monad stack. Useful when pure computations can fail—interpreters, compilers.

  • The "Reader" part lets you thread configuration through computations (e.g., compiler flags). It is like functional dependency injection with the configuration applied LAST.
  • The "Writer" part lets you thread logging through computations. You tell to log; messages are read at the end.
  • The "State" part lets you thread state through computations (e.g., variable aliases when unifying, symbols encountered).
  • The "Except" part gives you fail-fast semantics. It is based on Result but the API gives you throw, catch, and try which looks like imperative programming.

Use RWSE over RWS when you need fail-fast error handling.

(config -> state -> ( state, List log, Result error a ))
-> RWSE config log state error a

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

run :
RWSE config log state error a
-> config
-> state
-> ( state, List log, Result error a )

Execute the stack with config and state. Returns (finalState, logs, Result error a).

config
-> state
-> RWSE config log state error a
-> ( state, List log, Result error a )

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

pure : a -> RWSE config log state error a

Wrap a value in the stack.

fromResult : Result error a -> RWSE config log state error a

Create a stack from a Result. Ok becomes success; Err becomes a thrown error.

fromResult (Ok 42) -- succeeds with 42


fromResult (Err "fail") -- throws, halting the computation
doNothing : RWSE config log state error ()

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

Functor/Applicative/Monad

RWSE config log state error a
-> RWSE config log state error (a -> b)
-> RWSE config log state error b

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

(a -> RWSE config log state error b)
-> RWSE config log state error a
-> RWSE config log state error b

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

RWSE config log state error b
-> RWSE config log state error ()
-> RWSE config log state error b

Convenience function for where you have a computation that should happen after another where the preceding produces no value. It reads a lot nicer.

-- for example
pure ()
    |> andThen_ (tell [ "Hello" ])


-- is nicer than
pure ()
    |> andThen (\_ -> tell [ "Hello" ])
map :
(a -> b)
-> RWSE config log state error a
-> RWSE config log state error b

Transform the result of a stack.

(( a, config, state ) -> b)
-> RWSE config log state error a
-> RWSE config log state error b

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

(a -> b -> c)
-> RWSE config log state error a
-> RWSE config log state error b
-> RWSE config log state error c

Shorthand for andMapping.

map2 Tuple.pair ask get -- RWSE config log state error ( config, state )
(a -> b -> c -> d)
-> RWSE config log state error a
-> RWSE config log state error b
-> RWSE config log state error c
-> RWSE config log state error d

Shorthand for andMapping.

map3 f a b c


-- is equivalent to
pure f |> andMap a |> andMap b |> andMap c
(a -> b -> c -> d -> e)
-> RWSE config log state error a
-> RWSE config log state error b
-> RWSE config log state error c
-> RWSE config log state error d
-> RWSE config log state error 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)
-> RWSE config log state error a
-> RWSE config log state error b
-> RWSE config log state error c
-> RWSE config log state error d
-> RWSE config log state error e
-> RWSE config log state error 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
Bool
-> RWSE config log state error ()
-> RWSE config log state error ()

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

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

Execute a computation only when the Maybe has a value.

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

Execute a computation only when the Result is Ok.

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

Foldable/Traversable

List (RWSE config log state error a)
-> RWSE config log state error (List a)

ALSO KNOWN AS sequence

(a -> RWSE config log state error b)
-> List a
-> RWSE config log state error (List b)

Map each element through the stack and collect results. Also known as traverse. Fails fast on first error.

List a
-> (a -> RWSE config log state error ())
-> RWSE config log state error ()

Like a for loop: run a computation for each element, discarding results. Fails fast on first error.

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

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

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

Configuration (Reader)

ask : RWSE config log state error config

Load the configuration as the value.

PLEASE see the following before using this

  • andModify if you want to modify the state based on the result of a prior computation, the configuration, and the current state.
  • andTell if you want to log based on the result of a prior computation, the configuration, and the current state.
  • mapWith if you want to map the value with knowledge of the configuration and the current state.
asks : (config -> a) -> RWSE config log state error a

Load one fact of the configuration as the value.

PLEASE see the following before using this

  • andModify if you want to modify the state based on the result of a prior computation, the configuration, and the current state.
  • andTell if you want to log based on the result of a prior computation, the configuration, and the current state.
  • mapWith if you want to map the value with knowledge of the configuration and the current state.

Logging (Writer)

tell : List log -> RWSE config log state error ()

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

(( a, config, state ) -> List log)
-> RWSE config log state error a
-> RWSE config log state error 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 -> RWSE config log state error ()

Append a single value to the log. Equivalent to tell [ x ].

State

get : RWSE config log state error state

Read the state into the value returned by the state. Only use this if you genuinely need to read the state for some reason.

  • If you want to modify the state (read it, apply a function, and write it) use modify.
  • If you want to modify the state based on the result of a prior computation, the configuration, and the current state use andModify.
  • If you want to tell based on the result of a prior computation, the configuration, and the current state use andTell.
modify : (state -> state) -> RWSE config log state error ()

Update the state by applying an update function.

See andModify for a more powerful version that includes the current state, the result of a prior computation, and the configuration.

put : state -> RWSE config log state error ()

Replace the state entirely.

(( state, a, config ) -> state)
-> RWSE config log state error a
-> RWSE config log state error a

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

Exceptions

(error -> RWSE config log state error a)
-> RWSE config log state error a
-> RWSE config log state error a

Recover from an error. If the stack failed, run the handler with the error; otherwise pass through the success.

throw (ParseError "bad")
    |> catch (\err -> pure (defaultValue err))
throw : error -> RWSE config log state error a

Throw an error and halt all computations. Stops chains of andThen, combine, combineMap, for_, and foldM.

throw (ParseError "unexpected token")
    |> catch (\err -> pure 0)  -- recovers with 0
try :
RWSE config log state error a
-> RWSE config log state error (Result error a)

Turn a failed stack into a successful one by wrapping the result in Result. Errors become Err, successes become Ok.

try (throw (ParseError "oops")) -- Ok (Err (ParseError "oops"))


try (pure 42) -- Ok (Ok 42)

Transformations

Lens parentState childState
-> RWSE config log childState error a
-> RWSE config log parentState error 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))
RWSE config log parentState error a
-> Optional parentState childCaseState
-> RWSE config log childCaseState error a
-> RWSE config log parentState error 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.

RWSE config log customType error a
-> Prism customType constructorInfo
-> RWSE config log constructorInfo error a
-> RWSE config log customType error 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.

(ea -> eb)
-> RWSE config log state ea a
-> RWSE config log state eb a

Transform the error type. Useful when unifying error types from different subcomputations.

mapError (\e -> Wrapper e) (parseExpr ...)
ignore : RWSE config log state error a -> RWSE config log state error ()

Throw away a value.

Miscellaneous

askGet : RWSE config log state error ( config, state )

Convenience function to load the configuration and the state all in one fell swoop.