Ah, that makes sense.

You're right, I was removing the wrong runEffect...and I see why it was 
unnecessary :)

One last thing I am stuck on; what exactly does `view` do in this context? 
 And how does it know when to properly chunk up ByteStrings, as to keep 
constant space?

On Saturday, May 10, 2014 7:40:52 AM UTC-7, Gabriel Gonzalez wrote:
>
>  
> On 05/09/2014 09:21 PM, Justin Le wrote:
>  
> Thanks Gabriel!  This was very helpful :) 
>
>  I've updated the repo with my modifications, and I had a couple of 
> comments.
>
>  1. Deleting `runEffect` appears to bring about a type error, trying to 
> unify the Proxy type with IO ().  Did I do something wrong here?
>  
>
> Make sure you are deleting the correct `runEffect` (from encode.hs).  The 
> result of `freqs` is not an `Effect`, so you don't need to run it.
>
>  2. Is there anywhere I can read up on Consumer' and (>~)?  I sort of 
> have been avoided using them because I don't fully understand the 
> differences between the (>->) category and the (>~) category, actually.  
>  
>
> The English explanation of what `(>~)` does is that `p' >~ p` replaces 
> every `await` in `p` with `p'`.  So, for example, when you write:
>
>     p' >~ cat
>
> That's equivalent to:
>
>     forever $ do
>         a <- p'
>         yield a
>
> In other words, `p'` gets reused in its entirety every time the downstream 
> pipe `await`s.
>
> This behavior can also be specified more formally using these laws:
>
>     -- (p' >~) is a monad morphism
>     p' >~ (do
>         x <- m
>         f x )
>     = do
>         x <- p' >~ m
>         p' >~ f x
>
>     p' >~ return r = return 
>
>     -- `await` is the right-identity of `(>~)`
>     p' >~ await = p'
>
>     -- The next two equations are free theorems
>     p' >~ yield x = yield x
>
>     p' >~ lift m = lift m
>
> So, using the example of `p' >~ cat`, you can evaluate it like so:
>
>     p' >~ cat
>
>     -- Definition of `cat`
>     = p' >~ (forever $ do
>         a <- await
>         yield a )
>
>     -- Monad morphisms distribute over `forever`
>     = forever $ p' >~ (do
>         a <- await
>         yield a )
>
>     -- Monad morphism distributes over bind
>     = forever $ do
>         a <- p' >~ await
>         p' >~ yield a
>
>     -- p' >~ await = p'
>     = forever $ do
>         a <- p'
>         p' >~ yield a
>
>     -- p' >~ yield a = yield a
>     = forever $ do
>         a <- p'
>         yield a
>
> It may also help to read the "Consumers" section of the `pipes` tutorial:
>
> http://hackage.haskell.org/package/pipes-4.1.1/docs/Pipes-Tutorial.html#g:4
>
>
>  Consumer' is just Consumer, but with the output not technically "closed" 
> off for good (just effectively), right?  And how does (>~ cat) turn it into 
> a Pipe?
>
>  Thank you again!
>
>  Justin
>
> On Friday, May 2, 2014 4:06:07 PM UTC-7, Gabriel Gonzalez wrote: 
>>
>>  This is the perfect kind of question to post to the mailing list!
>>
>> I will go down the two programs and make minor comments and then review 
>> their overall structure.
>>
>> -- encode.hs
>>
>> * Delete `runEffect`.  It's not doing anything.  The reason that it still 
>> type-checked was because your base monad was polymorphic over `MonadIO`, so 
>> it let you accidentally insert an additional `Pipe` layer (which was not 
>> doing anything).  As a side note, I think I made a mistake by parametrizing 
>> the `Pipes.Prelude` utilities over `MonadIO` (I prefer using `hoist` now), 
>> but I don't want to make a breaking change to fix it.
>>
>> * Good use of `withFile` instead of `pipes-safe`.  I feel like too many 
>> people unnecessarily use `pipes-safe` when `withFile` suffices.
>>
>> * Use `view Pipes.ByteString.pack p` instead of `p >-> PP.map 
>> B.singleton`.  It will group your Word8's into a more efficient chunk 
>> size.  Your current formulation will call a separate write command for 
>> every single byte, which is very inefficient.
>>
>> * For the reverse direction (i.e. `bytes`), you can either:
>>
>> A) Use `view (from Pipes.ByteString.pack)`, but that requires a `lens` 
>> dependency (which I think is not good).  I plan to fix that by providing an 
>> `unpack` lens in an upcoming `pipes-bytestring` release.  I created an 
>> issue for this:
>>
>> https://github.com/Gabriel439/Haskell-Pipes-ByteString-Library/issues/36
>>
>> B) Use `mapFoldable`:
>>
>>     bytes = Pipes.Prelude.mapFoldable BS.unpack
>>
>> That's much more efficient.  The problem with your `bytes` function is 
>> that it uses `foldl`, which triggers a bunch of left-associated binds, 
>> generating quadratic time complexity in the number of bytes:
>>
>>     ((((return ()) >> yield byte1) >> yield byte2) >> yield byte3
>>
>> `mapFoldable`, on the other hand, is implemented in terms of `each`, 
>> which uses a right-fold like this:
>>
>>     each = Data.Foldable.foldr (\a p -> yield a >> p) (return ())
>>
>> ... which triggers build/fold fusion and also gives linear time 
>> complexity:
>>
>>     yield byte1 >> (yield byte2 >> (yield byte3 >> return ()))
>>
>> * If you're willing to skip the error message, you can shorten 
>> `encodeByte` to:
>>
>>     encodeByte t = for cat $ \b -> each (b `M.lookup` t)
>>
>> ... which is the same thing as:
>>
>>     encodeByte t = Pipes.Prelude.mapFoldable (`M.lookup` t)
>>
>> * I should probably provide a function that transforms `Parser`s to 
>> functions between `Producer`s to simplify your `dirsBytes` code.  I also 
>> find myself writing that same pattern way too many times.  I just created 
>> an issue to remind myself to do this:
>>
>> https://github.com/Gabriel439/Haskell-Pipes-Parse-Library/issues/28
>>
>> -- decode.hs
>>
>> * Is there any reason why you `drain` unused input using `limit` instead 
>> of just using `take` by itself?
>>
>> * Same thing as `encode`.hs: try using `Pipes.ByteString.pack` and 
>> `Pipes.Prelude.mapFoldable Data.ByteString.unpack` for much greater 
>> efficiency translating between `Word8`s and `ByteString`s
>>
>> * You can make the code for `searchPT` more reusable by first defining a 
>> `Consumer'` (note the prime!) that produces a single `Direction`, like this:
>>
>>      searchPT :: forall m. Monad m => PreTree Word8 -> Consumer' 
>> Direction m Word8
>>     searchPT pt0 = go pt0
>>       where
>>         go :: PreTree Word8 -> Consumer Direction m Word8
>>         go (PTLeaf x      ) = return x
>>         go (PTNode pt1 pt2) = do
>>             dir <- await
>>             go $ case dir of
>>                 DLeft  -> pt1
>>                 DRight -> pt2
>>
>> ... and then you can optionally upgrade that to a `Pipe` like this:
>>
>>     searchPT pt >~ cat :: Pipe Direction Word8 m r
>>
>> That decouples the logic for parsing one direction from the logic for 
>> looping.
>>
>> * Also, there's nothing `Word8`-specific about your `searchPT` function.  
>> Consider generalizing the type to any value.
>>
>> * You can simplify the implementation of `dirs` using `mapFoldable`:
>>
>>     dirs = Pipes.Prelude.mapFoldable byteToDirs
>>
>> Overall the architecture of your program looks correct.  I don't see any 
>> obvious non-idiomatic things that you are doing.
>>
>> On 5/2/14, 2:43 AM, Justin Le wrote:
>>  
>> Hi pipes people; 
>>
>>  I really don't know too much about pipes, but an entire section in a 
>> project tutorial I am writing is going to be dedicated to hooking up all of 
>> the pipes plumbing together.  Seeing as this might also be possibly used as 
>> a pipes tutorial, I just wanted to make sure that my pipes code is 
>> idiomatic/not awful/not going to set back your progress by generations. 
>>  Does anyone mind maybe giving it a quick look over? :)  I would really 
>> appreciate it, and credit will be given where deserved :)  I hope it is not 
>> too imposing for me to ask!
>>
>>  It's actually a pair of programs --- a Huffman compression encoder and 
>> decoder.
>>
>>  The encoder: 
>> https://github.com/mstksg/inCode/blob/master/code-samples/huffman/encode.hs
>> The decoer: 
>> https://github.com/mstksg/inCode/blob/master/code-samples/huffman/decode.hs
>>
>>  I tried my best to abstract away the actual mechanisms of the huffman 
>> logic where I could; it does peak in at some times, but the comments should 
>> give you a general high-level idea of what each function is trying to do. 
>>  For reference, the series itself explaining the logic is hosted at 
>> http://blog.jle.im/entries/series/+huffman-compression
>>
>>  I am pretty sure that the code gives away my unfamiliarity :)
>>
>>  Thank you all!
>> Justin
>>  -- 
>> You received this message because you are subscribed to the Google Groups 
>> "Haskell Pipes" group.
>> To unsubscribe from this group and stop receiving emails from it, send an 
>> email to [email protected].
>> To post to this group, send email to [email protected].
>>
>>
>>   -- 
> You received this message because you are subscribed to the Google Groups 
> "Haskell Pipes" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to [email protected] <javascript:>.
> To post to this group, send email to [email protected]<javascript:>
> .
>
>
>  

-- 
You received this message because you are subscribed to the Google Groups 
"Haskell Pipes" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].

Reply via email to