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 aListTanalog 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` stdinLnThat will type check as any
ListTout 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 useawaitcan 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
Categoryinstance 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
Consumers andPipes 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 originalCategorytype class if possible, possibly havingawaitand(>~)just be functions that wrap your type in a newtype, use theCategoryinstance, 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
Categoryinstance for their type wrapped in theConsumernewtype.Then you would get a constraint like this any time you used
awaitor(>~):