我正在研究monad变形金刚,我读了this所以关于如何避免lift
的帖子。
我的想法是MonadIO
是可以嵌入IO
的monad,MonadWriter w
是可以嵌入WriterT w
的monad。所以我编写了下面的代码(读取,累积和记录数字,直到我们得到零),其中使用显式lift
的工作版本在注释中。但GHC抱怨道。我做错了什么?
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.IO.Class
import Control.Monad.Writer.Class (MonadWriter)
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Writer
-- f :: ReaderT Int (WriterT [String] IO) Int
-- m1 = ReaderT, m2 = WriterT
f :: (MonadWriter [String] m1, MonadIO m2) => m1 (m2 (IO Int))
f = do
s <- liftIO getLine
tell ["Input: " ++ s] -- lift $ tell ["Input: " ++ s]
let i = read s :: Int
if i == 0
then ask
else local (+i) f
main = do
rst <- runWriterT $ runReaderT f 0
print rst
答案 0 :(得分:5)
我的想法是MonadIO是可以嵌入IO的monad,而MonadWriter是可以嵌入WriterT的monad。
这不完全正确。 MonadIO
可以使用liftIO
,MonadWriter
可以使用tell
。因此,如果您想在同一个上下文/ monad中使用liftIO
,tell
,ask
和local
而不提升,单 monad you use必须是所有这些的实例:
f :: ( MonadWriter [String] m -- monad supports tell :: [String] -> m ()
, MonadReader Int m -- monad supports ask :: m Int
, MonadIO m -- monad supports liftIO :: IO a -> m a
) => m Int -- only a single m
请注意,您无法使用transformer
,但mtl
可以自动解除。因此,进口也会发生变化:
import Control.Monad.Reader (runReaderT, MonadReader)
import Control.Monad.Writer (runWriterT, MonadWriter)
import Control.Monad.IO.Class (liftIO, MonadIO)
MonadIO
的导入不会发生变化,因为IO
操作永远不会自动解除。
顺便说一下,使用runWriterT
和runReaderT
已经消除了变换器堆栈的所有歧义,因为这将使用
ReaderT Int (WriterT [String] IO Int)