Fusing conduits with multiple inputs

后端 未结 2 1371
闹比i
闹比i 2021-01-02 09:23

I am trying to create a conduit that can consume multiple input streams. I need to be able to await on one or the other of the input streams in no particular order (e.g., no

2条回答
  •  没有蜡笔的小新
    2021-01-02 09:49

    If you want to combine two IO-generated streams, then Gabriel's comment is the solution.

    Otherwise, you can't wait for both streams, which one produces a value first. Conduits are single-threaded and deterministic - it processes only one pipe at a time. But you could create a function that interleaves two streams, letting them decide when to switch:

    {-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
    import Control.Monad (liftM)
    import Data.Conduit.Internal (
        Pipe (..), Source, Sink,
        injectLeftovers, ConduitM (..),
        mapOutput, mapOutputMaybe
      )
    
    -- | Alternate two given sources, running one until it yields `Nothing`,
    -- then switching to the other one.
    merge :: Monad m
          => Source m (Maybe a)
          -> Source m (Maybe b)
          -> Source m (Either a b)
    merge (ConduitM l) (ConduitM r) = ConduitM $ goL l r
      where
        goL :: Monad m => Pipe () () (Maybe a) () m () 
                       -> Pipe () () (Maybe b) () m ()
                       -> Pipe () () (Either a b) () m ()
        goL (Leftover l ()) r           = goL l r
        goL (NeedInput _ c) r           = goL (c ()) r
        goL (PipeM mx) r                = PipeM $ liftM (`goL` r) mx
        goL (Done _) r                  = mapOutputMaybe (liftM Right) r
        goL (HaveOutput c f (Just o)) r = HaveOutput (goL c r) f (Left o)
        goL (HaveOutput c f Nothing) r  = goR c r
        -- This is just a mirror copy of goL. We should combine them together to
        -- avoid code repetition.
        goR :: Monad m => Pipe () () (Maybe a) () m ()
                       -> Pipe () () (Maybe b) () m ()
                       -> Pipe () () (Either a b) () m ()
        goR l (Leftover r ())           = goR l r
        goR l (NeedInput _ c)           = goR l (c ())
        goR l (PipeM mx)                = PipeM $ liftM (goR l) mx
        goR l (Done _)                  = mapOutputMaybe (liftM Left) l
        goR l (HaveOutput c f (Just o)) = HaveOutput (goR l c) f (Right o)
        goR l (HaveOutput c f Nothing)  = goL l c
    

    It processes one source until it returns Nothing, then switches to another, etc. If one source finishes, the other one is processed to the end.

    As an example, we can combine and interleave two lists:

    import Control.Monad.Trans
    import Data.Conduit (($$), awaitForever)
    import Data.Conduit.List (sourceList)
    
    main =  (merge (sourceList $ concatMap (\x -> [Just x, Just x, Nothing]) [  1..10])
                   (sourceList $ concatMap (\x -> [Just x, Nothing]) [101..110]) )
             $$ awaitForever (\x -> lift $ print x)
    

    If you need multiple sources, merge could be adapted to something like

    mergeList :: Monad m => [Source m (Maybe a)] -> Source m a
    

    which would cycle through the given list of sources until all of them are finished.

提交回复
热议问题