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