管道是否有 `liftIO` 等价物?
Is there a `liftIO` equivalence for conduits?
我正在编写一个越来越大的管道,其中包含嵌套的 monad 转换。 lift
每次 yield
或 await
调用基础 conduitM
是一项乏味的工作。更不用说我每次添加或撤消一层转换时,我都需要在每个可能的位置更改 lift
的数量。
我一直在寻找与 liftIO
类似的函数,但不是提升 IO 操作,而是应该将 yield
或 await
提升为基于 [=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
,它定义了广义的 yield
和 await
函数,并为 ConduitM
和您在其上使用的每个其他转换器添加实例。我认为这不如尽可能重新排序堆栈优雅。
我认为更好的方法是使用包含的 Data.Conduit.Lift
模块。就像将所有 runMaybeT
、runStateT
等替换为 runMaybeC
和 runStateC
一样简单。瞧,ConduitM
被推到转换器堆栈的顶部。
我正在编写一个越来越大的管道,其中包含嵌套的 monad 转换。 lift
每次 yield
或 await
调用基础 conduitM
是一项乏味的工作。更不用说我每次添加或撤消一层转换时,我都需要在每个可能的位置更改 lift
的数量。
我一直在寻找与 liftIO
类似的函数,但不是提升 IO 操作,而是应该将 yield
或 await
提升为基于 [=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
,它定义了广义的 yield
和 await
函数,并为 ConduitM
和您在其上使用的每个其他转换器添加实例。我认为这不如尽可能重新排序堆栈优雅。
我认为更好的方法是使用包含的 Data.Conduit.Lift
模块。就像将所有 runMaybeT
、runStateT
等替换为 runMaybeC
和 runStateC
一样简单。瞧,ConduitM
被推到转换器堆栈的顶部。