View source on Github

Conduits released!

conduit Base package, including the Resource transformer. attoparsec-conduit Turn attoparsec parsers into Sinks. blaze-builder-conduit Convert a stream of Builders into a stream of ByteStrings. filesystem-conduit Traverse folders, and convenience adapters for the system-filepath package. zlib-conduit Compress and decompress streams of bytes.

Quick Review

Sinks

import Data.Conduit import qualified Data.Conduit.List as CL -- Get a single line from the stream. sinkLine :: Resource m => Sink Char m String sinkLine = sinkState id -- initial state, nothing at the beginning of the line push close where -- On a new line, return the contents up until here push front '

' = return $ StateDone Nothing $ front [] -- Just another character, add it to the front and keep going push front char = return $ StateProcessing $ front . (char:) -- Got an EOF before hitting a newline, just give what we have so far close front = return $ front [] -- Get all the lines from the stream, until we hit a blank line or EOF. sinkLines :: Resource m => Sink Char m [ String ] sinkLines = do line <- sinkLine if null line then return [] else do lines <- sinkLines return $ line : lines content :: String content = unlines [ "This is the first line." , "Here's the second." , "" , "After the blank." ] main :: IO () main = do lines <- runResourceT $ CL. sourceList content $$ sinkLines mapM_ putStrLn lines

This is the first line. Here's the second.

Types type SinkPush input m output = input -> ResourceT m ( SinkResult input m output) type SinkClose m output = ResourceT m output data SinkResult input m output = Processing ( SinkPush input m output) ( SinkClose m output) | Done ( Maybe input) output data Sink input m output = SinkNoData output | SinkData { sinkPush :: SinkPush input m output , sinkClose :: SinkClose m output } | SinkLift ( ResourceT m ( Sink input m output)) myReturn a = SinkData (\input -> return ( Done ( Just input) a)) (return a)

Sinks: no helpers import Data.Conduit import System.IO import Control.Monad.Trans.Resource import Control.Monad.IO.Class (liftIO) -- Consume all input and discard it. sinkNull :: Resource m => Sink a m () sinkNull = SinkData push close where push _ignored = return $ Processing push close close = return () -- Let's stream characters to a file. Here we do need some kind of -- initialization. We do this by initializing in a push function, -- and then returning a different push function for subsequent -- calls. By using withIO, we know that the handle will be closed even -- if there's an exception. sinkFile :: ResourceIO m => FilePath -> Sink Char m () sinkFile fp = SinkData pushInit closeInit where pushInit char = do (releaseKey, handle) <- withIO (openFile fp WriteMode ) hClose push releaseKey handle char closeInit = do -- Never opened a file, so nothing to do here return () push releaseKey handle char = do liftIO $ hPutChar handle char return $ Processing (push releaseKey handle) (close releaseKey handle) close releaseKey _ = do -- Close the file handle as soon as possible. return () -- And we'll count how many values were in the stream. count :: Resource m => Sink a m Int count = SinkData (push 0 ) (close 0 ) where push count _ignored = return $ Processing (push count') (close count') where count' = count + 1 close count = return count

Sinks: with helpers import Data.Conduit import System.IO import Control.Monad.IO.Class (liftIO) -- We never have to touch the release key directly, sinkIO automatically -- releases our resource as soon as we return IODone from our push function, -- or sinkClose is called. sinkFile :: ResourceIO m => FilePath -> Sink Char m () sinkFile fp = sinkIO (openFile fp WriteMode ) hClose -- push: notice that we are given the handle and the input (\handle char -> do liftIO $ hPutChar handle char return IOProcessing ) -- close: we're also given the handle, but we don't use it (\_handle -> return ()) -- And we'll count how many values were in the stream. count :: Resource m => Sink a m Int count = sinkState 0 -- The push function gets both the current state and the next input... (\state _ignored -> -- and it returns the new state return $ StateProcessing $ state + 1 ) -- The close function gets the final state and returns the output. (\state -> return state)

List functions import Data.Conduit import qualified Data.Conduit.List as CL import Control.Monad.IO.Class (liftIO) -- A sum function. sum' :: Resource m => Sink Int m Int sum' = CL. fold (+) 0 -- Print every input value to standard output. printer :: ( Show a, ResourceIO m) => Sink a m () printer = CL. mapM_ (liftIO . print) -- Sum up all the values in a stream after the first five. sumSkipFive :: Resource m => Sink Int m Int sumSkipFive = do CL. drop 5 CL. fold (+) 0 -- Print each input number and sum the total printSum :: ResourceIO m => Sink Int m Int printSum = do total <- CL. foldM go 0 liftIO $ putStrLn $ "Sum: " ++ show total return total where go accum int = do liftIO $ putStrLn $ "New input: " ++ show int return $ accum + int

Connecting main :: IO () main = runResourceT $ do res <- case printSum of SinkData push close -> loop [ 1 .. 10 ] push close SinkNoData res -> return res liftIO $ putStrLn $ "Got a result: " ++ show res where start ( SinkData push close) = loop [ 1 .. 10 ] push close start ( SinkNoData res) = return res start ( SinkLift msink) = msink >>= start loop [] _push close = close loop (x:xs) push close = do mres <- push x case mres of Done _leftover res -> return res Processing push' close' -> loop xs push' close' main :: IO () main = runResourceT $ do res <- CL. sourceList [ 1 .. 10 ] $$ printSum liftIO $ putStrLn $ "Got a result: " ++ show res

To be continued...