Single-stepping a conduit

conduit, haskell

Solution

It's not exactly the same type signature you provided, but:

import Data.Conduit
import Data.Conduit.Internal (Pipe (..), ConduitM (..))

newtype Automata i o m r = Automata (m ([o], Either r (i -> Automata i o m r)))

conduitStep :: Monad m => ConduitM i o m r -> Automata i o m r
conduitStep (ConduitM con0) =
    Automata $ go [] id con0
  where
    go _ front (Done r) = return (front [], Left r)
    go ls front (HaveOutput p _ o) = go ls (front . (o:)) p
    go ls front (NeedInput p _) =
        case ls of
            [] -> return (front [], Right $ conduitStep . ConduitM . p)
            l:ls' -> go ls' front (p l)
    go ls front (PipeM mp) = mp >>= go ls front
    go ls front (Leftover p l) = go (l:ls) front p

But just be careful with this approach:

- By keeping the output as a list, it's not constant memory.

- We're throwing away finalizers.

There's probably a way to provide a `ZipConduit` abstraction, similar to `ZipSource` and `ZipSink`, that would handle this kind of problem more elegantly, but I haven't thought about it too much.

EDIT I ended up implementing `ZipConduit` in conduit-extra 0.1.5. Here's a demonstration of using it which sounds a bit like your case:

import           Control.Applicative
import           Data.Conduit
import           Data.Conduit.Extra
import qualified Data.Conduit.List   as CL

conduit1 :: Monad m => Conduit Int m String
conduit1 = CL.map $ \i -> "conduit1: " ++ show i

conduit2 :: Monad m => Conduit Double m String
conduit2 = CL.map $ \d -> "conduit2: " ++ show d

conduit :: Monad m => Conduit (Either Int Double) m String
conduit = getZipConduit $
    ZipConduit (lefts =$= conduit1) *>
    ZipConduit (rights =$= conduit2)
  where
    lefts = CL.mapMaybe (either Just (const Nothing))
    rights = CL.mapMaybe (either (const Nothing) Just)

main :: IO ()
main = do
    let src = do
            yield $ Left 1
            yield $ Right 2
            yield $ Left 3
            yield $ Right 4
        sink = CL.mapM_ putStrLn
    src $$ conduit =$ sink

Problem

I want to do something along the lines of ArrowChoice, but with conduits. I want to await an Either value and then pass Left values to one conduit and Right values to another, and then merge the results back into an Either stream. Presumably this can be done by making the inner conduits like automata: turn a conduit into a function that takes an argument and returns a monadic list of outputs yielded: ``` newtype AutomataM i m o = Automata (i -> m (o, Automata i o)) conduitStep :: Conduit i m o -> AutomataM i m [o] ``` The reason for the list of outputs is that a Conduit may yield 0 or more outputs for each input. I've looked at ResumableConduit and its relatives, and presumably the answer is in there somewhere. But I can't quite see how its done.

Original source

Related problems