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

63 Views Asked by At

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 ?

1

There are 1 best solutions below

2
Daniel Wagner On BEST ANSWER

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