如何在另一个 Monad 中使用 IO Monad

how to use IO Monad in another Monad

我使用 MongoDB 库来处理来自 Mongodb 的数据。有一个名为 Action 的 Monad,代表一个 DB 读或写操作 https://github.com/TonyGen/mongoDB-haskell/blob/master/doc/tutorial.md。 但是,我发现当我在 monad Action 中时,我还想做一些必须在 IO Monad 中的 IO。一些代码如

-- `Action' is a Monad
--
intoFile :: String -> Cursor -> Action IO ()
intoFile ric c = do
  outH <- liftIO $ openFile ric AppendMode
  liftIO $ hPutStrLn outH "Some log"
  loopIntoFile outH c
  liftIO $ hClose outH

在任何 IO monad 之前都有一个 liftIO,我认为它可能很冗长。任何简洁的方法来处理这个?

您想携带 2 个额外的上下文 - IO 上下文和 Action 上下文。 monad 转换器就是这种情况,因为它们允许您处理分层 monad 并在 do 块内部选择要为所需操作选择的 monad。 Here is a great explanation 为什么我们需要它们以及如何使用它们。

您无法避免 liftIO,不幸的是,因为标准 IO 操作不会过载以在任何 MonadIO 中工作。但是您可以在一次调用 liftIO:

下加入一系列 IO 操作
intoFile :: String -> Cursor -> Action IO ()
intoFile ric c = do
  outH <- liftIO $ do
    openFile ric AppendMode
    hPutStrLn outH "Some log"
  loopIntoFile outH c
  liftIO $ hClose outH

或者,如果你打算重复使用相同的IO操作,你可以为它们引入辅助定义:

intoFile :: String -> Cursor -> Action IO ()
intoFile ric c = do
  outH <- openLog ric AppendMode
  log outH "Some log"
  loopIntoFile outH c
  closeLog outH

openLog path mode = liftIO (openFile path mode)
log handle message = liftIO (hPutStrLn handle message)
closeLog handle = liftIO (hClose handle)