I'm trying to learn the pipes
package by writing my own sum
function and I'm getting stumped. I'd like to not use the utility functions from Pipes.Prelude
(since it has sum
and fold
and other functions which make it trivial) and only use the information as described in Pipes.Tutorial
. The tutorial doesn't talk about the constructors of Proxy
, but if I look in the source of sum
and fold
it uses those constructors and I wonder whether it is possible to write my sum
function without knowledge of these low level details.
I'm having trouble coming to terms with how this function would be able to continue taking in values as long as there are values available, and then somehow return that sum to the user. I guess the type would be:
sum' :: Monad m => Consumer Int m Int
It appears to me this could work because this function could consume values until there are no more, then return the final sum. I would use it like this:
mysum <- runEffect $ inputs >-> sum'
However, the function in Pipes.Prelude
has the following signature instead:
sum :: (Monad m, Num a) => Producer a m () -> m a
So I guess this is my first hurdle. Why does the sum
function take a Producer
as an argument as opposed to using >->
to connect?
FYI I ended up with the following after the answer from danidiaz:
sum' = go 0
where
go n p = next p >>= \x -> case x of
Left _ -> return n
Right (_, p') -> go (n + 1) p'
Consumers
are actually quite limited in what they can do. They can't detect end-of-input (pipes-parse uses a different technique for that) and when some other part of the pipeline stops (for example the Producer
upstream) that part is the one that must provide the result value for the pipeline. So putting the sum in the return value of the Consumer
won't work in general.
Some alternatives are:
Implement a function that deals directly with Producer
internals, or perhaps uses an auxiliary function like next
. There are adapters of this type that can feed Producer
data to "smarter" consumers, like Fold
s from the foldl package.
Keep using a Consumer
, but instead of putting the sum in the return value of the Consumer
, use a WriterT
as the base monad with a Sum Int
monoid as accumulator. That way, even if the Producer
stop first, you can still run the writer to get to the accumulator This solution is likely to be less efficient, though.
Example code for the WriterT
approach:
import Data.Monoid
import Control.Monad
import Control.Monad.Trans.Writer
import Pipes
producer :: Monad m => Producer Int m ()
producer = mapM_ yield [1..10]
summator :: Monad n => Consumer Int (WriterT (Sum Int) n) ()
summator = forever $ await >>= lift . tell . Sum
main :: IO ()
main = do
Sum r <- execWriterT . runEffect $ producer >-> summator
print r
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With