The classic example of something easier to implement with conduit
currently is handling end of input from upstream. For example, if you want to fold a list of values and bind the result within the pipeline, you cannot do it within pipes
without engineering an extra protocol on top of pipes
.
In fact, this is precisely what the upcoming pipes-parse
library solves. It engineers a Maybe
protocol on top of pipes
and then defines convenient functions for drawing input from upstream that respect that protocol.
For example, you have the onlyK
function, which takes a pipe and wraps all outputs in Just
and then finishes with a Nothing
:
onlyK :: (Monad m, Proxy p) => (q -> p a' a b' b m r) -> (q -> p a' a b' (Maybe b) m r)
You also have the justK
function, which defines a functor from pipes that are Maybe
-unaware to pipes that are Maybe
-aware for backwards compatibility
justK :: (Monad m, ListT p) => (q -> p x a x b m r) -> (q -> p x (Maybe a) x (Maybe b) m r)
justK idT = idT
justK (p1 >-> p2) = justK p1 >-> justK p2
And then once you have a Producer
that respects that protocol you can use a large variety of parsers that abstract over the Nothing
check for you. The simplest one is draw
:
draw :: (Monad m, Proxy p) => Consumer (ParseP a p) (Maybe a) m a
It retrieves a value of type a
or fails in the ParseP
proxy transformer if upstream ran out of input. You can also take multiple values at once:
drawN :: (Monad m, Proxy p) => Int -> Consumer (ParseP a p) (Maybe a) m [a]
drawN n = replicateM n draw -- except the actual implementation is faster
... and several other nice functions. The user never actually has to directly interact with the end of input signal at all.
Usually when people ask for end-of-input handling, what they really wanted was parsing, which is why pipes-parse
frames end-of-input issues as a subset of parsing.