
On Mon, Apr 16, 2012 at 10:13 PM, Ben Franksen
I like your pipes package. This is very similar to what Mario Blažević wrote about his Coroutines in the Monad.Reader (can't remember which issue; great article, BTW, many thanks Mario for making me understand the iteratee business (and also generators) for the first time). Your pipes-core looks even simpler to use, maybe due to avoiding to make a type distinction between consumer/producer/pipe (except the natural one i.e. through the input/output types), even though the parameterization by a functor (as in Monad.Coroutine) has its own beauty.
Yes, Mario Blažević's Transducer is the main inspiration for the original pipes package, and consequently pipes-core.
Two issues:
(1) What is the reason for the asymmetry in
type Producer b m = Pipe () b m type Consumer a m = Pipe a Void m
i.e. why does Producer use () for the input? I would expect it to use Void, like Consumer does for its output. Calling await in a Producer resulting in an immediate 'return ()' as you say is allowed (in the tutorial) strikes me as not very useful.
The underlying reason for the asymmetry is the fact that '()' is a terminal object in the category of haskell types and *total* functions, while 'Void' is an initial object. Here's a property that uniquely determines the definitions of 'Producer' above. Let 'X' be the type such that 'Producer b m = Pipe X b m'. For all producers 'p' there should be a unique (total) pipe 'alpha :: forall a r. Pipe a X m r' such that 'alpha >+> p' and 'p' are observationally equal. In other words, since a producer "never uses values of its input type 'a'", there should be a unique way to make it into a pipe which is polymorphic in 'a'. It's easy to see that this property immediately implies that 'X' should be a terminal object, i.e. '()', and 'alpha' is therefore 'pipe (const ())'. Dually, you obtain that 'Consumer a m' is necessarily 'Pipe a Void m', and 'alpha = pipe absurd'.
(2) The $$ operator is poorly named. I would intuitively expect an operator that looks so similar to the standard $ to have the same direction of data flow (i.e. right to left, like function application and composition) but your is left to right. You could use e.g. >$> instead, which has the additional advantage of allowing a symmetric variant for the other direction i.e. <$<.
'$$' is inspired by iteratees. Similarly to its iteratee counterpart, it discards upstream result values and only returns the output of the last pipe. That said, '>$>' looks like a clearer alternative, so I could consider changing it. Thanks for your feedback! Paolo