Haskell:为什么这个monad转换错了?

sqd*_*sqd 3 monads haskell functional-programming monad-transformers

我正在研究monad变形金刚,我读了这篇关于如何避免lifts的帖子.

我的想法是,MonadIO有单子在其中IO可以嵌入,并MonadWriter w有单子在其中WriterT w可以嵌入.所以我编写了下面的代码(读取,累积和记录数字,直到我们得到零),其中使用explicit的工作版本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
Run Code Online (Sandbox Code Playgroud)

Zet*_*eta 5

我的想法是MonadIO是可以嵌入IO的monad,而MonadWriter是可以嵌入WriterT的monad.

这不完全正确.MonadIOs可以使用liftIO,也MonadWriter可以使用tell.因此,如果你想使用liftIO,tell,asklocal在没有升降相同的上下文/单子,在单一使用单子一定是所有的人的一个实例:

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
Run Code Online (Sandbox Code Playgroud)

请注意,您不能使用transformer,但mtl要自动提升.因此,进口也会发生变化:

import Control.Monad.Reader (runReaderT, MonadReader)
import Control.Monad.Writer (runWriterT, MonadWriter)
import Control.Monad.IO.Class (liftIO, MonadIO)
Run Code Online (Sandbox Code Playgroud)

MonadIO由于IO操作永远不会自动解除,因此导入不会更改.

顺便说一句,你使用runWriterTrunReaderT已经消除了变压器堆栈的所有歧义,因为这将使用

ReaderT Int (WriterT [String] IO Int)
Run Code Online (Sandbox Code Playgroud)