Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a Haskell Pipes "sum" function?

Tags:

haskell

pipe

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'
like image 1000
Ana Avatar asked Feb 01 '16 20:02

Ana


1 Answers

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 Folds 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
like image 120
danidiaz Avatar answered Sep 17 '22 14:09

danidiaz