单子变换器——显式提升

3

我正在阅读《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

我(认为我)理解了如何简单地调用askgetput,因为它们在MonadReaderMonadWriterMonadState类型类中被定义,并且存在实例,例如MonadWriter(StateT s m)等。

但是,我不明白为什么无法显式地lift来自下一层的操作到当前的单子变换器。在constrainedCount中,我处于Reader单子中,如果我理解正确,我认为st <- getst <- lift get都应该起作用。(而且telllift . lift . tell应该是相同的)。如果我将st <- get更改为st <- lift get,那么就会出现错误。

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

这告诉我非常少... 我对此的理解完全错误吗?

如果我没记错的话,get 不需要任何参数,所以 lift . get 是行不通的,因为 (.) 接受两个函数并将它们组合起来。你尝试过不使用组合,直接使用 lift get 吗? - bheklilr
(可能)回答你的问题,我认为这是因为它被包装在一个新类型中。虽然我不知道为什么你想要明确地“lift”操作。 - bheklilr
我不想用“真正”的代码来做这件事 - 我只是想测试一下我的理解。 - beta
1个回答

9

让我们来看一下lift get的类型:

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

但是你的 MyApp 不是一个单子变换器,它只是一个单子。不过里面的东西当然是单子变换器,所以如果你使用

    st <- MyA $ lift get

它有效。


2
啊,对了。那 确实 有道理。如果我把 MyApp 改成 type App = WriterT [(FilePath,Int)] (StateT AppState (ReaderT AppConfig IO)),它会按照预期工作,然后我就可以使用,例如,lift get - beta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接