[Haskell-cafe] 'Progress bar' enumeratee

Gregory Collins greg at gregorycollins.net
Wed Apr 6 21:39:20 CEST 2011


My enumerator style may not be the best (I'm long-winded), but
personally when the stream types are the same on input and output I
often skip the Enumeratee stuff and just write an Enumerator wrapper.
To address your complaint here:

> PS Implementations which involve "EB.take count" seem to me unsatisfactory;
> one surely oughtn't need to have a large buffer to solve this problem

I'd write a helping combinator:

> module Main where
>
> import           Control.Monad              (when)
> import           Control.Monad.Trans
> import           Data.ByteString.Char8      (ByteString)
> import qualified Data.ByteString.Lazy.Char8 as L
> import           Data.Enumerator
> import qualified Data.Enumerator.List       as EL
> import           System.IO
>
> takeUpTo :: Monad m =>
>             Int
>          -> Iteratee ByteString m (Stream ByteString, Int)
> takeUpTo n' = continue k
>   where
>     n = toEnum n'
>
>     k EOF         = yield (EOF,0) EOF
>     k (Chunks xs) = if taken == 0
>                       then takeUpTo n'
>                       else yield (stream, taken) rest
>       where
>         s      = L.fromChunks xs
>         (a,b)  = L.splitAt n s
>         taken  = fromEnum $ L.length a
>         stream = Chunks $ L.toChunks a
>         rest   = Chunks $ L.toChunks b
>

The code to run a side effect every N bytes is then pretty short (and
should be efficient):

> sideEffectEveryNBytes :: Monad m =>
>                          Int      -- ^ run the side effect every N bytes
>                       -> m ()     -- ^ side effect
>                       -> Step ByteString m a
>                       -> Iteratee ByteString m a
> sideEffectEveryNBytes n act = flip checkContinue1 n $ \loop i k -> do
>         (str, taken) <- takeUpTo i
>         when (taken == i) $ lift act
>         (lift $ runIteratee $ k str) >>= loop (nextI $ i - taken)
>   where
>     nextI 0 = n
>     nextI i = i

Here's your particular example:

> example :: IO [ByteString]
> example = run_ $ enumList 1 [ "the quick brown "
>                             , "fox "
>                             , "jumped "
>                             , "over "
>                             , "the lazy dog" ] $$ it
>   where
>     it = do
>         xs <- sideEffectEveryNBytes 10 (putStr "." >> hFlush stdout) $$
>               EL.consume
>         lift $ putStrLn ""
>         return xs

Running it:

> *Main> example
> ....
> ["the quick ","brown ","fox ","jumped ","ove","r ","the lazy"," dog"]

G
-- 
Gregory Collins <greg at gregorycollins.net>



More information about the Haskell-Cafe mailing list