This can be done by plunging into the interior of the pipeline. I wanted to avoid this because it looked very dirty. Based on the answers here, there seems to be no way around this (but I really appreciate a cleaner solution).
The key difficulty is that (x =$=) is a pure function, but in order for transPipe to give the correct answer, it needs some kind of function with stateful, function-like function:
data StatefulMorph mn = StatefulMorph { stepStatefulMorph :: forall a. ma -> n (StatefulMorph mn, a) , finalizeStatefulMorph :: n () }
The StatefulMorph mn step takes a value in m and returns in n both this value and the next StatefulMorph , which should be used to convert the next value of m . The last StatefulMorph must be finalized (which, in the case of "stateful (x =$=) " terminates channel x .
Formation merging can be implemented as StatefulMorph , using the code for pipeL with minor changes. Signature:
fuseStateful :: Monad m => Conduit amb -> StatefulMorph (ConduitM bcm) (ConduitM acm)
I also need a replacement for transPipe (a special case of hoist ), which uses StatefulMorph values โโinstead of functions.
class StatefulHoist t where statefulHoist :: (Monad m, Monad n) => StatefulMorph mn -> tmr -> tnr
A StatefulHoist instance for ConduitM io can be written using transPipe code with some minor changes.
fuseInner then easy to implement.
fuseInner :: Monad m => Conduit amb -> ConduitM io (ConduitM bcm) r -> ConduitM io (ConduitM acm) r fuseInner left = statefulHoist (fuseStateful left)
I wrote a more detailed explanation here and posted the full code. If someone can come up with a cleaner solution, or one that uses the public conduit API, submit it.
Thanks for all the suggestions and input!