Question

I'm playing around with conduit library, and have written a sample piece of code for extracting two numbers (2 & 3) when they appear in a sequence. Following is my code:

import Data.Conduit
import qualified Data.Conduit.List as CL

source = CL.sourceList [1,2,3,4,5,2,3] :: Source IO Int

-- Extract the consequent 2 and 3 number
extract23 :: Conduit Int IO Int
extract23 = do
  a <- await
  b <- await
  case (a,b) of
    (Just a,Just b) ->
      if a == 2 && b == 3
      then do yield a
              yield b
              extract23
      else extract23
    _ -> return ()

conduit1 :: Conduit Int IO String
conduit1 = CL.map show

sink1 :: Sink String IO ()
sink1 = CL.mapM_ putStrLn

main :: IO ()
main = source $= (extract23 =$= conduit1) $$ sink1

But when I execute the main function, I get no output. What I expect actually is something like this:

2
3
2
3

Any idea on what I'm doing wrong ?

Was it helpful?

Solution

Your code calls await twice in a row. What that says is "give me the next two values in the stream, and do not put them back in the stream." When you do this repeatedly, you are essentially breaking your stream into 2-value chunks. Using your original list, you are basically getting tuples that look like:

[(1,2),(3,4),(5,2)] -- final 3 is lost since it has no pair

The issue is that you 2,3 sequences always fall between two of these tuples. It seems to me like the algorithm you really want is:

  • Check if the first two values in the stream match 2,3.
  • Proceed forward in the stream by one element and repeat.

Currently, you're stepping forward two elements in the stream.

Fortunately, there's an easy solution to this problem: instead of using await to get the second value, which removes it from the stream at the same time, use peek, which will look at the value and put it back. If you replace b <- await with b <- CL.peek, you should get the behavior you're looking for.

UPDATE

Just to give a little bit more information. Under the surface, peek is implemented on top of two primitives in conduit: await and leftover, like so:

peek = do
    mx <- await
    case mx of
        Nothing -> return Nothing
        Just x -> do
            leftover x
            return (Just x)

There's nothing magical about this ability to look ahead by 1 element. You can similarly look ahead 2 elements. The only trick is making sure to do the leftovers in the correct order:

peek2 = do
    mx <- await
    my <- await
    maybe (return ()) leftover my
    maybe (return ()) leftover mx
    return (mx, my)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top