A Pipe
can be broken into two parts: the generator part (yield
) and the consumer part (await
).
If you have a Pipe
that only uses it's generator half, and only returns ()
(or never returns), then it can be represented as a "ListT
done right". It turns out that MonadPlus
can be used to represent anything like ListT-done-right. Quoting Gabriel Gonzalez:
Note that you can build any
ListT
(not just the one in pipes) with only a transformers dependency. For example, here is how you would implement aListT
analog ofPipes.Prelude.stdinLn
:-- stdinLn :: ListT IO String stdinLn :: (MonadTrans t, MonadPlus (t IO)) => t IO String stdinLn = do eof <- lift isEOF if eof then mzero else do str <- lift getLine return str `mplus` stdinLn
That will type check as any
ListT
out there and do the right thing for all of them.
So my question is this: Is there a dual to ListT
and to MonadPlus
for the consumer portion of Pipes?
Requirements:
- A pipe which never uses
yield
, and only returns()
(or never returns), but does useawait
can be represented as this "dual to ListT". - The "dual to ListT" can be generalized to the "dual of MonadPlus"
I think the answer is not to dualize the "generator-like" type-class, but rather to extend it with a simple
Category
instance equivalent to theawait
/(>~)
category ofpipes
.Unfortunately, there is no way to arrange the type variables to make this satisfy all three type classes (
MonadPlus
,MonadTrans
, andCategory
), so I will define a new type class:The laws for this type class are the category laws:
Then you can implement both
Consumer
s andPipe
s once you have this additional type class:The hard part is figuring out how to do this without adding a new type class to
base
. I'd prefer to reuse the originalCategory
type class if possible, possibly havingawait
and(>~)
just be functions that wrap your type in a newtype, use theCategory
instance, and then unwrap it, but I'm still working out the specifics of how to do that.Edit: I found the solution. Just define the following newtype:
Then any library can just implement a
Category
instance for their type wrapped in theConsumer
newtype.Then you would get a constraint like this any time you used
await
or(>~)
: