Simplifying if statement in a StateT monad transformer While learning the basics of Haskell I decided to challenge myself with seemingly simple task - write a program that would push the user input into a stack if it's not an empty string or pop the value from the stack and print it to user if the input is an empty string. the program would exit if it tries to pop from an empty stack.

Realising that I would have to combine functionality of input/output, stateful computations and failure handling got me looking into Monad Transformers. So here is the code that I ended up with:

import Control.Monad    
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Control.Monad.Trans.Maybe 
import Control.Monad.IO.Class

type Stack = [String]

pop :: Stack -&gt; (Maybe String, Stack)
pop (x:xs)  = (Just x, xs)
pop []      = (Nothing, [])

push :: String -&gt; Stack -&gt; ((), Stack)
push s xs = ((), s:xs)

mainLoop :: StateT Stack (MaybeT IO) ()
mainLoop = do
    input &lt;- liftIO getLine
    if null input
         then do
             Just x &lt;- state pop
             liftIO . putStrLn $ x
         else state $ push input
    mainLoop

main = runMaybeT (runStateT mainLoop [])


It works exactly how I wanted, but can someone please look into how I can simplify this code? Or if it's even possible to simplify it? Especially the then branch.
I don't like your pop and push. They're shaped specially for StateT, but leave off the wrapper. I'd suggest, instead, something like this:

pop :: Monad m
    =&gt; StateT [a] m (Maybe a)
pop = do
  s &lt;- get
  case s of
    x : s' -&gt; Just x &lt;$ put s'
    [] -&gt; pure Nothing

push :: Monad m
     =&gt; a -&gt; StateT [a] ()
push a = modify (a :)


Of course, you can implement those using state, but I think this way is clearer. Once you've gone that far, you might consider using Control.Monad.State.Strict (from mtl) and changing the type signatures:

pop :: MonadState [a] m
    =&gt; m (Maybe a)
push :: MonadState [a] m
     =&gt; a -&gt; m a


Side note: Control.Monad.Trans.State implements lazy state transformers, which are rather weird beasts you usually don't need. Unless you have a special reason, reach for Control.Monad.Trans.State.Strict or Control.Monad.State.Strict instead.

Moving on with the mtl approach: StateT s (MaybeT m) a is really

s -&gt; MaybeT m (a, s)


or

s -&gt; m (Maybe (a, s))


What about MaybeT (StateT s m) a? That's

StateT s m (Maybe a)


or

s -&gt; m (Maybe a, s)


Not quite the same, but pretty similar! Their monadic operations are essentially the same. The big difference is that in the latter case you get the last state whether or not the operation stopped short. I think you may end up happier that way, with

mainLoop :: MaybeT (StateT Stack IO) x
mainLoop = do
  input &lt;- liftIO getLine
  if null input
    then do
      -- No pattern match!
      x &lt;- MaybeT pop
      liftIO . putStrLn $ x
    else push input
  mainLoop


Extra side note: we can make mainLoop polymorphic in its return value, because it never returns.



An alternative:

pop :: (MonadState [a] m, MonadFail m)
    =&gt; m a
pop = do
  s &lt;- get
  case s of
    x : s' -&gt; x &lt;$ put s'
    [] -&gt; fail "Empty stack"


Now you can use the same mainLoop code whichever order you choose to stack the transformers:

mainLoop :: ( MonadState Stack m
            , MonadIO m
            , MonadFail m )
         =&gt; m x
mainLoop = do
  input &lt;- liftIO getLine
  if null input
    then do
      x &lt;- pop
      liftIO . putStrLn $ x
    else push input
  mainLoop


In my opinion, though, this is less clear than the other options, and probably doesn't add much useful flexibility.