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 .