Monad transformer – Explicit lifting

442 views Asked by At

I'm reading about monad transformers in Real World Haskell. In the following example, the stack is Writer on top State on top of Reader on top of IO.

{-# Language GeneralizedNewtypeDeriving #-}

import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Writer
import System.Directory
import System.FilePath

data AppConfig = AppConfig {
      cfgMaxDepth :: Int
    } deriving Show

data AppState = AppState {
      stDeepestReached :: Int
    } deriving Show

newtype MyApp a = MyA {
      runA :: WriterT [(FilePath,Int)] (StateT AppState (ReaderT AppConfig IO)) a
    } deriving (Monad, MonadIO, Functor, MonadReader AppConfig,
                MonadWriter [(FilePath,Int)], MonadState AppState)

runApp :: MyApp a -> Int -> IO ([(FilePath,Int)], AppState)
runApp k maxDepth = let config = AppConfig maxDepth
                        state' = AppState 0
                     in runReaderT (runStateT (execWriterT $ runA k) state') config

constrainedCount :: Int -> FilePath -> MyApp ()
constrainedCount curDepth path = do
  contents <- liftIO . getDirectoryContents $ path
  cfg <- ask
  let maxDepth = cfgMaxDepth cfg
  tell [(path,curDepth)]
  forM_ (filter (\d' -> d' /= ".." && d' /= ".") contents) $ \d -> do
    let newPath = path </> d
    isDir <- liftIO $ doesDirectoryExist newPath
    when (isDir && curDepth < maxDepth) $ do
         let newDepth = curDepth+1
         st <- get
         when (stDeepestReached st < newDepth) $
             put st { stDeepestReached = newDepth }
         constrainedCount newDepth newPath

main = runApp (constrainedCount 0 "/tmp") 2 >>= print

I (think I) understand how I can simply call ask, get and put since these are defined in the MonadReader, MonadWriter and MonadState typeclasses and there are instances such as MonadWriter (StateT s m) and so on.

What I don't understand is why I cannot explicit lift an action from the layer below up to the current monad transformer. In constrainedCount I'm in the Reader monad, if I understand correctly, and I thought both st <- get and st <- lift get should work. (And that tell and lift . lift . tellshould be the same). If I changest <- gettost <- lift get` I get the error

Couldn't match type `t0 m0' with `MyApp'
Expected type: MyApp ()
Actual type: t0 m0 ()

which tells me very little... Is my understanding of this completely wrong?

1

There are 1 answers

1
Petr On BEST ANSWER

Let's have a look at the type of lift get:

lift get :: (MonadTrans t, MonadState a m) => t m a

But your MyApp isn't a monad transformer, it's only a monad. But what's inside is, of course, so if you use

    st <- MyA $ lift get

it works.