管道是否有 `liftIO` 等价物?

Is there a `liftIO` equivalence for conduits?

我正在编写一个越来越大的管道,其中包含嵌套的 monad 转换。 lift 每次 yieldawait 调用基础 conduitM 是一项乏味的工作。更不用说我每次添加或撤消一层转换时,我都需要在每个可能的位置更改 lift 的数量。

我一直在寻找与 liftIO 类似的函数,但不是提升 IO 操作,而是应该将 yieldawait 提升为基于 [=19 的任意转换的 monad =],但我似乎找不到。有没有办法实现这样的目标?


编辑:针对@BradleyHardy的回答,我这里举个具体的例子:

{-# LANGUAGE LambdaCase #-}

import Control.Monad                    as MON 
import Control.Monad.IO.Class           as MIO 
import Control.Monad.Trans.Class        as MTC 
import Control.Monad.Trans.Maybe        as MTM 
import Data.Conduit                     as CDT 
import System.IO                        as IO

stdinS :: Source IO String
stdinS = void . runMaybeT . forever $ do
    (liftIO isEOF) >>= \case
        True  -> mzero
        False -> (lift . yield) =<< (liftIO getLine)

myK :: Sink String IO ()
myK = void . runMaybeT . forever . runMaybeT $ do
    a <- maybe (lift mzero) return =<< (lift . lift $ await)
    b <- if a == "listen"
      then maybe (lift mzero) return =<< (lift . lift $ await)
      else mzero
    liftIO . putStrLn $ "I heard: " ++ b

main :: IO ()
main = do
    stdinS $$ myK 

如何更改 myK 以将 ConduitM 置于堆栈顶部?不可否认,在这个特定示例中使用 MaybeT 过于复杂,但在我的实际(更大)管道中 MaybeT 结构比例如更清晰递归。

ConduitM 本身 一个 monad 转换器,并且,查看它的 Haddock page,我们看到它定义了 [=11= 的实例], MonadReader, 等等。这应该表明实际上预期的模式是将 ConduitM 放在转换器堆栈的顶部,在这种情况下,您不需要将任何操作提升到其中。

事实上,它甚至为 MonadBase 定义了一个实例,class 允许您从位于堆栈最底部的 monad 中提取操作(使用 liftBase 函数),所以如果重新排序你的堆栈意味着很难访问底部的 new 东西,MonadBase 会为你解决这个问题。如果你在底部有 IO,这可能不是很有帮助。

我建议您尝试重新排序变压器堆栈,如果可能的话将 ConduitM 放在最上面。

编辑: 另一种选择是创建自己的 class、MonadConduit,它定义了广义的 yieldawait 函数,并为 ConduitM 和您在其上使用的每个其他转换器添加实例。我认为这不如尽可能重新排序堆栈优雅。

我认为更好的方法是使用包含的 Data.Conduit.Lift 模块。就像将所有 runMaybeTrunStateT 等替换为 runMaybeCrunStateC 一样简单。瞧,ConduitM 被推到转换器堆栈的顶部。