Skip to the content.

Calculating a sum in pipes

In the previous post, I wrote some functions that group values in a pipe. Now, I’d like to calculate a sum in a pipe.

Obviously, we can apply Prelude.sum to each list of values like this.

import Control.Monad (forever)
import Data.Maybe (catMaybes)
import Pipes
import qualified Pipes.Prelude as P

source :: Monad m => Producer Int m ()
source = each [1..9]

test :: Int -> IO ()
test n = runEffect $ (source >-> P.map Just >> forever (yield Nothing)) >->
                     (forever $ sequence (replicate n await) >>= yield) >->
                     P.map catMaybes >->
                     P.takeWhile (not . null) >->
                     P.map sum >->
                     P.print

Of course, we can calculate a sum in an intermediate pipe instead of calculating it in the last pipe.

import Control.Monad (forever)
import Data.Foldable (foldMap)
import Data.Maybe (fromJust, isJust)
import Data.Monoid (Monoid, Sum(Sum), getSum)
import Pipes
import qualified Pipes.Prelude as P
import Prelude hiding (sum)

source :: Monad m => Producer Int m ()
source = each [1..9]

--sum :: Num a => [Maybe a] -> Maybe a
--sum :: (Functor t, Foldable t, Num a) => t (Maybe a) -> Maybe a
sum :: (Functor t, Foldable t, Functor f, Monoid (f (Sum a))) => t (f a) -> f a
sum = fmap getSum . foldMap (fmap Sum)

test :: Int -> IO ()
test n = runEffect $ (source >-> P.map Just >> forever (yield Nothing)) >->
                     (forever $ sequence (replicate n await) >>= yield . sum) >->
                     P.takeWhile isJust >->
                     P.map fromJust >->
                     P.print

With pipes-parse, we can fold values into a sum of the values. Note that we split the producer so that it just yields a specified number of values using splitAt with zoom.

import Data.Foldable (for_)
import Lens.Family2.State.Strict (zoom)
import Pipes
import Pipes.Parse
import qualified Pipes.Prelude as P
import Prelude hiding (splitAt, sum)

source :: Monad m => Producer Int m ()
source = each [1..9]

sum :: (Monad m, Num a) => Int -> Producer a m r -> Producer a m ()
sum n producer = do
    (m, producer') <- lift $ runStateT parser producer
    for_ m $ \m -> do
        yield m
        sum n producer'
  where
    parser = zoom (splitAt n) $ foldAll add Nothing id
    add (Just x) y = Just $ x + y
    add Nothing y = Just y

test :: Int -> IO ()
test n = runEffect $ sum n source >-> P.print

Finally, with pipes-group, we can use Pipes.Group.folds to fold values in each chunk.

import Lens.Family2 (view)
import Pipes
import Pipes.Group
import qualified Pipes.Prelude as P
import Prelude hiding (sum)

source :: Monad m => Producer Int m ()
source = each [1..9]

sum :: (Monad m, Num a) => Int -> Producer a m r -> Producer a m r
sum n = folds (+) 0 id . view (chunksOf n)

test :: Int -> IO ()
test n = runEffect $ sum n source >-> P.print

With foldl, you can use Control.Foldl.sum instead of writing a folding function by yourself.

import qualified Control.Foldl as F
import Lens.Family2 (view)
import Pipes
import Pipes.Group
import qualified Pipes.Prelude as P
import Prelude hiding (sum)

source :: Monad m => Producer Int m ()
source = each [1..9]

sum :: (Monad m, Num a) => Int -> Producer a m r -> Producer a m r
sum n = F.purely folds F.sum . view (chunksOf n)

test :: Int -> IO ()
test n = runEffect $ sum n source >-> P.print