Why should we use the state monad instead of the direct state?

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

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

against

 bar2 :: State Foo Foo bar2 = do modify (\(Foo x) -> Foo (x + 1)) get 
+9
haskell monads purely-functional state-monad
source share
3 answers

Going through the state is often tedious, error prone and impedes refactoring. For example, try marking a binary tree or a rosewood in a postoperator:

 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 it (ts', i'') = gots i' ts 

Here, I had to manually state the states in the correct order, pass the correct states, and require that the labels and child nodes be in the correct order as a result (note that the naive use of foldr or foldl for child nodes can lead to incorrect behavior).

In addition, if I try to change the code to pre-order, I have to make changes that are easily mistaken:

 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 it (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 State Monad Decisions:

 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') 

This code is not only shorter, but also easier to write, the logic that leads to marking before or after ordering is much 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 
+14
source share

As an example for comment above, you can write code using the State monad, for example

 {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} import Data.Text (Text) import qualified Data.Text as Text import Control.Monad.State data MyState = MyState { _count :: Int , _messages :: [Text] } deriving (Eq, Show) makeLenses ''MyState type App = State MyState incrCnt :: App () incrCnt = modify (\my -> my & count +~ 1) logMsg :: Text -> App () logMsg msg = modify (\my -> my & messages %~ (++ [msg])) logAndIncr :: Text -> App () logAndIncr msg = do incrCnt logMsg msg app :: App () app = do logAndIncr "First step" logAndIncr "Second step" logAndIncr "Third step" logAndIncr "Fourth step" logAndIncr "Fifth step" 

Please note that using additional operators from Control.Lens also allows you to write incrCnt and logMsg as

 incrCnt = count += 1 logMsg msg = messages %= (++ [msg]) 

which is another advantage of using State in combination with the lens library, but for comparison, I do not use them in this example. To write the equivalent code above by simply passing an argument, it will be more like

 incrCnt :: MyState -> MyState incrCnt my = my & count +~ 1 logMsg :: MyState -> Text -> MyState logMsg my msg = my & messages %~ (++ [msg]) logAndIncr :: MyState -> Text -> MyState logAndIncr my msg = let incremented = incrCnt my logged = logMsg incremented msg in logged 

This is not so bad at the moment, but as soon as we move on to the next step, I think you will see where the code duplication actually takes place:

 app :: MyState -> MyState app initial = let first_step = logAndIncr initial "First step" second_step = logAndIncr first_step "Second step" third_step = logAndIncr second_step "Third step" fourth_step = logAndIncr third_step "Fourth step" fifth_step = logAndIncr fourth_step "Fifth step" in fifth_step 

Another advantage of wrapping this in a Monad instance is that you can use the full power of Control.Monad and Control.Applicative :

 app = mapM_ logAndIncr [ "First step", "Second step", "Third step", "Fourth step", "Fifth step" ] 

This allows you to significantly increase the flexibility in processing values ​​calculated at runtime, compared with static values.

The difference between passing the state manually and using the State monad is that the State monad is an abstraction of the manual process. It also happens that it corresponds to several other more commonly used more general abstractions, such as Monad , Applicative , Functor and several others. If you also use a StateT transformer, then you can combine these operations with other monads, such as IO . Can you do all this without State and StateT ? Of course, you can, and no one bothers you there, but the fact is that State abstracts this template and gives you access to a huge set of tools from more general tools. In addition, a small modification of the types described above allows you to use the same functions in several contexts:

 incrCnt :: MonadState MyState m => m () logMsg :: MonadState MyState m => Text -> m () logAndIncr :: MonadState MyState m => Text -> m () 

Now they will work with the App or with StateT MyState IO or any other monad stack with the implementation of MonadState . This makes it much more reusable than simply passing arguments, which is only possible through the abstraction of StateT .

+5
source share

In my experience, the point of many Monads doesn't actually click until you go into larger examples, so here is an example of using State (well, StateT ... IO ) to parse an incoming request for a web service.

The example is that this web service can be called up with many options of different types, although all but one of the parameters have decent default values. If I receive an incoming JSON request with an unknown key value, I must refuse the corresponding message. I use state to keep track of what the current configuration is, and what the rest of the JSON request is, as well as many access methods.

(Based on the code that is currently running, with changed names and details of what this service is really hiding)

 {-# LANGUAGE OverloadedStrings #-} module XmpConfig where import Data.IORef import Control.Arrow (first) import Control.Monad import qualified Data.Text as T import Data.Aeson hiding ((.=)) import qualified Data.HashMap.Strict as MS import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.State (execStateT, StateT, gets, modify) import qualified Data.Foldable as DF import Data.Maybe (fromJust, isJust) data Taggy = UseTags Bool | NoTags newtype Locale = Locale String data MyServiceConfig = MyServiceConfig { _mscTagStatus :: Taggy , _mscFlipResult :: Bool , _mscWasteTime :: Bool , _mscLocale :: Locale , _mscFormatVersion :: Int , _mscJobs :: [String] } baseWebConfig :: IO (IORef [String], IORef [String], MyServiceConfig) baseWebConfig = do infoRef <- newIORef [] warningRef <- newIORef [] let cfg = MyServiceConfig { _mscTagStatus = NoTags , _mscFlipResult = False , _mscWasteTime = False , _mscLocale = Locale "en-US" , _mscFormatVersion = 1 , _mscJobs = [] } return (infoRef, warningRef, cfg) parseLocale :: T.Text -> Maybe Locale parseLocale = Just . Locale . T.unpack -- The real thing does more parseJSONReq :: MS.HashMap T.Text Value -> IO (IORef [String], IORef [String], MyServiceConfig) parseJSONReq m = liftM snd (baseWebConfig >>= (\c -> execStateT parse' (m, c))) where parse' :: StateT (MS.HashMap T.Text Value, (IORef [String], IORef [String], MyServiceConfig)) IO () parse' = do let addWarning s = do let snd3 (_, b, _) = b r <- gets (snd3 . snd) liftIO $ modifyIORef r (++ [s]) -- These two functions suck a key/value off the input map and -- pass the value on to the handler "h" onKey kh = onKeyMaybe k $ DF.mapM_ h onKeyMaybe kh = do myb <- gets fst modify $ first $ MS.delete k h (MS.lookup k myb) -- Access the "lns" field of the configuration config setter = modify (\(a, (b, c, d)) -> (a, (b, c, setter d))) onKey "tags" $ \x -> case x of Bool True -> config $ \c -> c {_mscTagStatus = UseTags False} String "true" -> config $ \c -> c {_mscTagStatus = UseTags False} Bool False -> config $ \c -> c {_mscTagStatus = NoTags} String "false" -> config $ \c -> c {_mscTagStatus = NoTags} String "inline" -> config $ \c -> c {_mscTagStatus = UseTags True} q -> addWarning ("Bad value ignored for tags: " ++ show q) onKey "reverse" $ \x -> case x of Bool r -> config $ \c -> c {_mscFlipResult = r} q -> addWarning ("Bad value ignored for reverse: " ++ show q) onKey "spin" $ \x -> case x of Bool r -> config $ \c -> c {_mscWasteTime = r} q -> addWarning ("Bad value ignored for spin: " ++ show q) onKey "language" $ \x -> case x of String s | isJust (parseLocale s) -> config $ \c -> c {_mscLocale = fromJust $ parseLocale s} q -> addWarning ("Bad value ignored for language: " ++ show q) onKey "format" $ \x -> case x of Number 1 -> config $ \c -> c {_mscFormatVersion = 1} Number 2 -> config $ \c -> c {_mscFormatVersion = 2} q -> addWarning ("Bad value ignored for format: " ++ show q) onKeyMaybe "jobs" $ \p -> case p of Just (Array x) -> do q <- parseJobs x config $ \c -> c {_mscJobs = q} Just (String "test") -> config $ \c -> c {_mscJobs = ["test1", "test2"]} Just other -> fail $ "Bad value for jobs: " ++ show other Nothing -> fail "Missing value for jobs" m' <- gets fst unless (MS.null m') (fail $ "Unrecognized key(s): " ++ show (MS.keys m')) parseJobs :: (Monad m, DF.Foldable b) => b Value -> m [String] parseJobs = DF.foldrM (\ab -> liftM (:b) (parseJob a)) [] parseJob :: (Monad m) => Value -> m String parseJob (String s) = return (T.unpack s) parseJob q = fail $ "Bad job value: " ++ show q 
+1
source share

All Articles