Search code examples
haskellstate-monadreader-monad

One more time...can I have an example of state monad that does what I want?


I'm trying to understand the actual need for the reader and/or state monads. All the examples I've seen (including many on stackoverflow as I've hunted for suitable examples I can use and in various books and blog articles) are of the form (pseudo code)

 f = do
        foo <- ask
        do something with foo

 g = do
        foo <- ask
        do something else using foo

 h = runReader
       (
           f
           g
       )

In other words, call two functions and (presumably) maintain some state from one call to the next. However, I don't find this example particularly convincing as (I think) I could just have f return some state and then pass that state on to g.

I'd love to see an example, using a single integer (say) as the state to be preserved where, rather than two sequential calls to f and then g from a central place, rather there's a call to f which then internally calls g and then have changed state (if state monad) available back in the main routine.

Most (well actually all) the examples I have seen spend a tremendous amount of time focusing on the definition of the monad and then show how to set up a single function call. To me, it would be the ability to do nested calls and have the state carried along for the ride to demonstrate why it's useful.


Solution

  • Almost everything you can do with monads you can do without them. (Well, some are special like ST, STM, IO, etc., but that's a different story.) But:

    • they allow you to encapsulate many common patterns, like in this case stateful computations, and hide details or boiler-plate code that would be otherwise needed; and
    • there are plethora of functions that work on any (or many) monads, which you can just specialize for a particular monad you're using.

    To give an example: Often one needs to have some kind of a generator that supplies unique names, like when generating code etc. This can be easily accomplished using the state monad: Each time newName is called, it outputs a new name and increments the internal state:

    import Control.Monad.State
    import Data.Tree
    import qualified Data.Traversable as T
    
    type NameGen = State Int
    
    newName :: NameGen String
    newName = state $ \i -> ("x" ++ show i, i + 1)
    

    Now let's say we have a tree that has some missing values. We'd like to supply them with such generated names. Fortunately, there is a generic function mapM that allows to traverse any traversable structure with any monad (without the monad abstraction, we wouldn't have this function). Now fixing the tree is easy. For each value we check if it's filled (then we just use return to lift it into the monad), and if not, supply a new name:

    fillTree :: Tree (Maybe String) -> NameGen (Tree String)
    fillTree = T.mapM (maybe newName return)
    

    Just imagine implementing this function without monads, with explicit state - going manually through the tree and carrying the state around. The original idea would be completely lost in boilerplate code. Moreover, the function would be very specific to Tree and NameGen.

    But with monads, we can go even further. We could parametrize the name generator and construct even more generic function:

    fillTreeM :: (Monad m) => m String -> Tree (Maybe String) -> m (Tree String)
    fillTreeM gen = T.mapM (maybe gen return)
    

    Note the first parameter m String. It's not a constant String value, it's a recipe for generating a new String within m, whenever it's needed.

    Then the original one can be rewritten just as

    fillTree' :: Tree (Maybe String) -> NameGen (Tree String)
    fillTree' = fillTreeM newName
    

    But now we can use the same function for many very different purposes. For example, use the Rand monad and supply randomly generated names.

    Or, at some point we might consider a tree without filled out nodes invalid. Then we just say that wherever we're asked for a new name, we instead abort the whole computation. This can be implemented just as

    checkTree :: Tree (Maybe String) -> Maybe (Tree String)
    checkTree = fillTreeM Nothing
    

    where Nothing here is of type Maybe String, which, instead of trying to generate a new name, aborts the computation within the Maybe monad.

    This level of abstraction would be hardly possible without having the concept of monads.