我正在编写一个增长很大的管道,使用嵌套的monad变换。 lift
每次yield
或await
调用基地conduitM
是一项繁琐的工作。更不用说每次添加或撤销转换层时,我都需要在每个可能的位置更改lift
的数量。
我一直在寻找与liftIO
类似的功能,但不是解除IO操作,而是应该将yield
或await
提升为基于ConduitM
的任意转换后的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
中,结构比例如更清晰。递归。
答案 0 :(得分:3)
ConduitM
本身是一个monad转换器,并且查看它的Haddock page,我们看到它定义了MonadState
的实例,{{1这应该表明,实际上预期的模式是在变换器堆栈的顶部有MonadReader
,在这种情况下,您不需要将任何操作提升到它。
实际上,它甚至为MonadBase定义了一个实例,该类允许您从位于堆栈底部的monad(使用ConduitM
函数)提升操作,以便如果重新排序您的堆栈意味着很难访问底部的 new ,liftBase
会为您解决此问题。如果你最后有MonadBase
,那么这可能不是很有帮助。
我建议您尝试重新排序变压器堆栈,以便尽可能将IO
放在首位。
编辑:另一种选择是创建自己的类ConduitM
,该类定义广义的MonadConduit
和yield
函数,并为{{添加实例1}}以及您在其上使用的所有其他变换器。如果可能的话,我认为这不如重新排序你的堆栈那么优雅。
答案 1 :(得分:0)
我认为更好的方法是使用包含的Data.Conduit.Lift
模块。就像将runMaybeT
,runStateT
等所有内容替换为runMaybeC
和runStateC
一样简单。瞧,ConduitM
被推到变压器堆栈的顶部。