Shorter Run Functions

Last time around, I discussed 'run' functions and how these are the "entrypoint" for using most monads. However, it's also useful to have a couple potential shortcuts up our sleeve. Today we'll go over a couple "shortcut" functions when you don't need everything the monad supplies.

Recall that with the Writer and State monads, the run function produces two outputs. The first is the "result" of our computation (some type a). The second is the stateful value tracked by the monad:

runWriter :: Writer w a -> (a, w)

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

There are times however, especially with State, where the stateful value is the result. There are no shortage of functions out there that look like State s (). They have essentially no return value, but they update the tracked value:

doubleAndAddInput :: Int -> State Int ()
doubleAndAddInput x = do
  modify (* 2)
  modify (+ x)

Now let's think about running this computation. If we use runState, we'll end up with a tuple where one of the elements is just the unit ().

>> runState (doubleAndAddInput 5) 6
((), 17)

Of course we can always just use snd to ignore the first element of the tuple. But for the sake of code cleanliness it's nice to know that there are helper functions to skip this for you! Here are the exec functions for these two monads. They will return only the tracked state value!

execWriter :: Writer w a -> w

execState :: State s a -> s -> s

So applying this with our example above, we would get the following:

>> execState (doubleAndAddInput 5) 6
17

On the flip side, there are also times where you don't care about the accumulated state. All you care about is the final result! In this case, the function you want for the State monad is evalState:

evalState :: State s a -> s -> a

So now we could supply a return value in our function:

doubleAndAddInput :: Int -> State Int Int
doubleAndAddInput x = do
  prev <- get
  put (2 * prev + x)
  return (x + prev)

Then we can drop the accumulated state like so:

>> evalState (doubleAndAddInput 5) 6
11

For whatever reason, there doesn't seem to be evalWriter in the library. Perhaps the logic here is that the accumulated Writer value doesn't affect the computation, so if you're going to ignore its output, it has no effect. However, I could imagine cases where you originally wrote the function as a Writer, but in a particular usage of it, you don't need the value. So it's an interesting design decision.

Anyways, these functions also exist with monad transformers of course:

execStateT :: StateT s m a -> s -> m s

evalStateT :: StateT s m a -> s -> m a

execWriterT :: WriterT w m a -> m w

Next time we'll dig into monad transformers a bit more! In the meantime, learn more about monads by taking a look at our series on Monads and Functional Structures!

Previous
Previous

Does your Monad even Lift?

Next
Next

Running with Monads