Search code examples
haskellmonads

How can I use `liftIO` with State to print values inside that Monad?


I am trying to print inside the State monad by using liftIO function from MonadIO class:

import Control.Monad.State
import Control.Monad.IO.Class

import Control.Applicative

facHelper :: Integer -> State Integer ()
facHelper 0 = pure ()
facHelper n = do
  currentState <- get
  liftIO $ putStrLn $ "n = " ++ show currentState
  modify (*n)
  facHelper (n-1)

factorial :: Integer -> Integer
factorial n = snd (runState (facHelper n) 1)

main :: IO ()
main = print $ factorial 6

However, I get the error:

No instance for (MonadIO Data.Functor.Identity.Identity) arising from a use of ‘liftIO’

If I look up MonadIO class:

I see an instance:

MonadIO m => MonadIO (StateT s m)

And also see that State s is a type alias

type State s = StateT s Identity

So, in principle, I could use liftIO with State. Where is the problem ?


Solution

  • If you want to do IO, then IO has to be at the bottom of your monad transformer stack. Here's the usual mtl-way of fixing things:

    facHelper :: (MonadIO m, MonadState Integer m) => Integer -> m ()
    -- same definition as before
    
    factorial :: Integer -> IO Integer
    factorial n = execStateT (facHelper n) 1
    
    main :: IO ()
    main = factorial 6 >>= print
    

    This uses StateT Integer IO instead of State Integer (i.e. StateT Integer Identity).

    If you're curious about the mechanical details of what went wrong with your approach:

    1. want MonadIO (State s)
    2. have type State s = StateT s Identity, therefore want MonadIO (StateT s Identity)
    3. have MonadIO m => MonadIO (StateT s m), and more specifically have MonadIO Identity => MonadIO (StateT s Identity), therefore want MonadIO Identity
    4. don't (and can't sensibly) have MonadIO Identity