Haskell enumerator: analog to iteratees `enumWith` operator?
Asked Answered
C

1

6

Earlier today I wrote a small test app for iteratees that composed an iteratee for writing progress with an iteratee for actually copying data. I wound up with values like these:

-- NOTE: this snippet is with iteratees-0.8.5.0
-- side effect: display progress on stdout
displayProgress :: Iteratee ByteString IO ()

-- side effect: copy the bytestrings of Iteratee to Handle
fileSink :: Handle -> Iteratee ByteString IO ()

writeAndDisplayProgress :: Handle -> Iteratee ByteString IO ()
writeAndDisplayProgress handle = sequence_ [fileSink handle, displayProgress]

In looking at the enumerator library, I don't see an analog of sequence_ or enumWith. All I want to do is compose two iteratees so they act as one. I could discard the result (it's going to be () anyway) or keep it, I don't care. (&&&) from Control.Arrow is what I want, only for iteratees rather than arrows.

I tried these two options:

-- NOTE: this snippet is with enumerator-0.4.10
run_ $ enumFile source $$ sequence_ [iterHandle handle, displayProgress]
run_ $ enumFile source $$ sequence_ [displayProgress, iterHandle handle]

The first one copies the file, but doesn't show progress; the second one shows progress, but doesn't copy the file, so obviously the effect of the built-in sequence_ on enumerator's iteratees is to run the first iteratee until it terminates and then run the other, which is not what I want. I want to be running the iteratees in parallel rather than serially. I feel like I'm missing something obvious, but in reading the wc example for the enumerator library, I see this curious comment:

-- Exactly matching wc's output is too annoying, so this example
-- will just print one line per file, and support counting at most
-- one statistic per run

I wonder if this remark indicates that combining or composing iteratees within the enumerations framework isn't possible out of the box. What's the generally-accepted right way to do this?

Edit:

It seems as though there is no built-in way to do this. There's discussion on the Haskell mailing list about adding combinators like enumSequence and manyToOne but so far, there doesn't seem to be anything actually in the enumerator package that furnishes this capability.

Codeclination answered 14/7, 2011 at 5:39 Comment(5)
I didn't spend long enough looking at the documentation to formulate and test a real answer, but it looks superficially like Enumerators and Enumeratees are composable in the way you want. (P.S. I'm guessing you're talking about the "enumerator" package on Hackage. You should really say something like that up front, because there's now like half a dozen different implementations of iteratees. =)Bowhead
You also didn't spend enough time reading my question.Codeclination
That's a bit rude, considering I've just spent a bit of my time helping you solve your problem. Instead of waxing sarcastic, maybe you could point to the part you think I missed.Bowhead
If you look at my code comments, you'll see I clearly stated which version of which library the code snippet worked with, and you'll also see that I've been clear that what I'm trying to compose are iteratees, not enumeratees or enumerators, which the documentation does show how to combine. I'm not trying to be rude, but if you want to be helpful, you should read the whole question and understand it rather than just tell me to RTFM, which I've obviously already done. If the answer were this obvious, why would it take 3 days for someone to say "it's in the manual?"Codeclination
Hah! You're absolutely right about the library versions. I'm sorry about that -- my eyes tend to walk right over comments, and I guess that really bit me this time. However, I don't think it was clear that you were definitely aware that it was possible to compose enumerators; in fact, the last two sentences, "I wonder if... composing iteratees... isn't possible out of the box. What's the generally-accepted right way to do this?" can meaningfully be answered "the generally-accepted right way to do this is by composing enumerators instead of iteratees". I'm sorry that wasn't helpful to you.Bowhead
D
2

It seems to me like rather than trying to have two Iteratees consume the sequence in parallel, it would be better to feed the stream through an identity Enumeratee that simply counts the bytes passing it.

Here's a simple example that copies a file and prints the number of bytes copied after each chunk.

import System.Environment
import System.IO
import Data.Enumerator
import Data.Enumerator.Binary (enumFile, iterHandle)
import Data.Enumerator.List (mapAccumM)
import qualified Data.ByteString as B

printBytes :: Enumeratee B.ByteString B.ByteString IO ()
printBytes = flip mapAccumM 0 $ \total bytes -> do
    let total' = total + B.length bytes
    print total'
    return (total', bytes)

copyFile s t = withBinaryFile t WriteMode $ \h -> do
    run_ $ (enumFile s $= printBytes) $$ iterHandle h

main = do
    [source, target] <- getArgs
    copyFile source target
Dusen answered 17/7, 2011 at 20:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.