管道是否有“liftIO”等价?

时间:2015-11-07 01:18:52

标签: haskell monad-transformers

我正在编写一个增长很大的管道,使用嵌套的monad变换。 lift每次yieldawait调用基地conduitM是一项繁琐的工作。更不用说每次添加或撤销转换层时,我都需要在每个可能的位置更改lift的数量。

我一直在寻找与liftIO类似的功能,但不是解除IO操作,而是应该将yieldawait提升为基于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中,结构比例如更清晰。递归。

2 个答案:

答案 0 :(得分:3)

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

实际上,它甚至为MonadBase定义了一个实例,该类允许您从位于堆栈底部的monad(使用ConduitM函数)提升操作,以便如果重新排序您的堆栈意味着很难访问底部的 new liftBase会为您解决此问题。如果你最后有MonadBase,那么这可能不是很有帮助。

我建议您尝试重新排序变压器堆栈,以便尽可能将IO放在首位。

编辑:另一种选择是创建自己的类ConduitM,该类定义广义的MonadConduityield函数,并为{{添加实例1}}以及您在其上使用的所有其他变换器。如果可能的话,我认为这不如重新排序你的堆栈那么优雅。

答案 1 :(得分:0)

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