简体   繁体   中英

Is there a `liftIO` equivalence for conduits?

I'm writing a conduit that is growing quite big, with nested monad transforms. It's a tedious work to lift each and every yield or await call into the base conduitM . Not to mention every time I add or withdraw a layer of transformation, I need to change the number of lift 's at every possible location.

I was looking for a similar function as liftIO , but instead of lifting an IO operation it should lift yield or await into an arbitrarily transformed monad based on ConduitM , but I can't seem to find one. Is there a way to achieve something like this?


Edit: in response to @BradleyHardy's answer, here I provide a concrete example:

{-# 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 

How would you change myK to put ConduitM on top of the stack? Admittedly in this particular example using MaybeT is over-complication, but in my actual (much larger) conduit MaybeT structure is much cleaner than eg recursions.

ConduitM is itself a monad transformer, and, looking at the Haddock page for it, we see that it defines instances for MonadState , MonadReader , etc. This should suggest that in fact the pattern intended is to have ConduitM at the top of the transformer stack, in which case you don't need to lift any operations into it.

In fact, it even defines an instance for MonadBase , the class which lets you lift operations from the monad which sits at the very base of the stack (using the liftBase function), so that if reordering your stack means it's hard to access the new thing at the bottom, MonadBase solves this for you. If you have IO at the bottom anyway though, this probably isn't very helpful.

I would suggest that you try to reorder your transformer stack to put ConduitM on top if possible.

EDIT: Another option would be to create your own class, MonadConduit , which defines generalised yield and await functions, and add instances for ConduitM and every other transformer you use on top of it. I think that this is less elegant than reordering your stack if possible though.

I think a better way is to use the included Data.Conduit.Lift module. It is as simple as replacing all your runMaybeT , runStateT , etc. into runMaybeC and runStateC . And voila, the ConduitM is pushed to the top of the transformer stack.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM