Трансформатор монад — явный подъем

Я читаю о преобразователях монад в Real World Haskell. В следующем примере стек Writer сверху State поверх Reader поверх 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

Я (думаю, я) понимаю, как я могу просто вызывать ask, get и put, поскольку они определены в классах типов MonadReader, MonadWriter и MonadState и есть такие экземпляры, как MonadWriter (StateT s m) и так далее.

Чего я не понимаю, так это того, почему я не могу явным образом lift выполнить действие от уровня ниже до текущего преобразователя монады. В constrainedCount я в монаде Reader, если я правильно понимаю, и я думал, что и st <- get, и st <- lift get должны работать. (А то tell и поднять. поднять. сказатьshould be the same). If I changeст ‹- получитьtoст ‹- поднять получить` я получаю ошибку

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

что говорит мне очень мало... Мое понимание этого совершенно неверно?


person beta    schedule 29.08.2013    source источник
comment
IIRC, get не принимает никаких параметров, поэтому lift . get не будет работать, потому что (.) принимает две функции и объединяет их. Вы пробовали lift get без состава?   -  person bheklilr    schedule 29.08.2013
comment
Ух, мой плохой. Спасибо! Я обновил вопрос сейчас.   -  person beta    schedule 30.08.2013
comment
Чтобы (возможно) ответить на ваш вопрос, я думаю, это потому, что он завернут в новый тип. Я не знаю, почему вы хотите явно lift действия.   -  person bheklilr    schedule 30.08.2013
comment
Я бы не хотел делать это в «настоящем» коде — я просто хотел сделать это, чтобы проверить свое понимание.   -  person beta    schedule 30.08.2013


Ответы (1)


Давайте посмотрим на тип lift get:

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

Но ваш MyApp не преобразователь монад, это всего лишь монада. Но то, что внутри есть, конечно, так что если использовать

    st <- MyA $ lift get

оно работает.

person Petr    schedule 29.08.2013
comment
Ах. Правильно. Это действительно имеет смысл. Если я перехожу с MyApp на type App = WriterT [(FilePath,Int)] (StateT AppState (ReaderT AppConfig IO)), он работает, как и ожидалось, и я могу использовать, например. lift get. - person beta; 30.08.2013