[Haskell-cafe] 'Progress bar' enumeratee
David Hotham
david.hotham at blueyonder.co.uk
Wed Apr 6 11:53:26 CEST 2011
Hello,
I've spent some time over the last couple of days trying to write an
enumeratee that prints a "." every n bytes (with obvious intended use as a
progress tracker). Seems like it oughtn't be hard, but it has been a steep
learning curve...
I have come up with something that seems to do the job but I don't know that
I'm completely happy with it (or even that I completely understand it, to be
honest).
If anyone more expert would be kind enough either to reassure me that I'm
doing it right or - more likely - to offer improvements / suggestions on
what obvious simplifications I have overlooked, I'd be grateful.
Thanks
David
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.Enumerator as E
import qualified Data.Enumerator.Binary as EB
import Control.Monad.IO.Class (liftIO, MonadIO)
import System.IO (hFlush, stdout)
dotEvery :: MonadIO m => Integer -> E.Enumeratee B.ByteString B.ByteString m
b
dotEvery count = E.checkDone $ E.continue . dotIn count where
dotIn need k E.EOF = E.yield (E.Continue k) E.EOF
dotIn need k (E.Chunks []) = E.continue (dotIn need k)
dotIn need k (E.Chunks xs) = iter where
lazy = BL.fromChunks xs
len = toInteger $ BL.length lazy
iter = if len < need
then k (E.Chunks xs) E.>>==
E.checkDone (E.continue . dotIn (count - len))
else let (x1, x2) = BL.splitAt (fromInteger need) lazy
s1 = E.Chunks $ BL.toChunks x1
s2 = E.Chunks $ BL.toChunks x2
enumee = E.checkDoneEx s2 (\k' -> dotIn count k' s2)
in E.Iteratee $ do newStep <- E.runIteratee $ k s1
liftIO $ putStr "." >> hFlush stdout
E.runIteratee $ enumee newStep
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
PPS I did find an implementation via mapAccumM which I consider pleasing
enough from an aesthetic point of view - but which runs 30x slower
dotAt :: MonadIO m => Integer -> Integer -> Word8 -> m (Integer, Word8)
dotAt n s w | s >= n = do liftIO $ putStr "." >> hFlush stdout
return (1, w)
| otherwise = return (s+1, w)
dotEvery' :: MonadIO m => Integer -> E.Enumeratee B.ByteString B.ByteString
m b
dotEvery' n = EB.mapAccumM (dotAt n) 1
More information about the Haskell-Cafe
mailing list