我正在编写一个越来越大的管道,其中包含嵌套的 monad 转换。将每个 yield
或 await
调用lift
调用到基础 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
结构比例如更清晰递归。
最佳答案
ConduitM
本身 是一个 monad 转换器,并且查看 Haddock page对于它,我们看到它定义了 MonadState
、MonadReader
等的实例。这应该表明,实际上该模式旨在拥有 ConduitM
在转换器堆栈的顶部,在这种情况下,您不需要将任何操作提升到其中。
事实上,它甚至为MonadBase定义了一个实例。 ,该类允许您从位于堆栈最底部的 monad 中提升操作(使用 liftBase
函数),因此如果重新排序您的堆栈意味着很难访问 new 底部的东西,MonadBase
为您解决了这个问题。如果你在底部有 IO
,这可能不是很有用。
我建议您尝试重新排序您的转换器堆栈,以便尽可能将 ConduitM
放在顶部。
编辑:另一种选择是创建您自己的类 MonadConduit
,它定义了广义的 yield
和 await
函数,并为 ConduitM
和您在其上使用的所有其他转换器添加实例。我认为这不如尽可能重新排序堆栈优雅。
关于haskell - 管道是否有 `liftIO` 等价物?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33578055/