Search code examples
haskellmonadsstate-monadpurely-functional

Why must we use state monad instead of passing state directly?


Can someone show a simple example where state monad can be better than passing state directly?

bar1 (Foo x) = Foo (x + 1)

vs

bar2 :: State Foo Foo
bar2 = do
  modify (\(Foo x) -> Foo (x + 1))
  get

Solution

  • State passing is often tedious, error-prone, and hinders refactoring. For example, try labeling a binary tree or rose tree in postorder:

    data RoseTree a = Node a [RoseTree a] deriving (Show)
    
    postLabel :: RoseTree a -> RoseTree Int
    postLabel = fst . go 0 where
      go i (Node _ ts) = (Node i' ts', i' + 1) where
    
        (ts', i') = gots i ts
    
        gots i []     = ([], i)
        gots i (t:ts) = (t':ts', i'') where
          (t', i')   = go i t
          (ts', i'') = gots i' ts
    

    Here I had to manually label states in the right order, pass the correct states along, and had to make sure that both the labels and child nodes are in the right order in the result (note that naive use of foldr or foldl for the child nodes could have easily led to incorrect behavior).

    Also, if I try to change the code to preorder, I have to make changes that are easy to get wrong:

    preLabel :: RoseTree a -> RoseTree Int
    preLabel = fst . go 0 where
      go i (Node _ ts) = (Node i ts', i') where -- first change
    
        (ts', i') = gots (i + 1) ts -- second change
    
        gots i []     = ([], i)
        gots i (t:ts) = (t':ts', i'') where
          (t', i')   = go i t
          (ts', i'') = gots i' ts
    

    Examples:

    branch = Node ()
    nil  = branch []
    tree = branch [branch [nil, nil], nil]
    preLabel tree == Node 0 [Node 1 [Node 2 [],Node 3 []],Node 4 []]
    postLabel tree == Node 4 [Node 2 [Node 0 [],Node 1 []],Node 3 []]
    

    Contrast the state monad solution:

    import Control.Monad.State
    import Control.Applicative
    
    postLabel' :: RoseTree a -> RoseTree Int
    postLabel' = (`evalState` 0) . go where
      go (Node _ ts) = do
        ts' <- traverse go ts
        i   <- get <* modify (+1)
        pure (Node i ts')
    
    preLabel' :: RoseTree a -> RoseTree Int
    preLabel' = (`evalState` 0) . go where
      go (Node _ ts) = do
        i   <- get <* modify (+1)
        ts' <- traverse go ts
        pure (Node i ts')
    

    Not only is this code more succinct and easier to write correctly, the logic that results in pre- or postorder labeling is far more transparent.


    PS: bonus applicative style:

    postLabel' :: RoseTree a -> RoseTree Int
    postLabel' = (`evalState` 0) . go where
      go (Node _ ts) =
        flip Node <$> traverse go ts <*> (get <* modify (+1))
    
    preLabel' :: RoseTree a -> RoseTree Int
    preLabel' = (`evalState` 0) . go where
      go (Node _ ts) =
        Node <$> (get <* modify (+1)) <*> traverse go ts