如何编写Haskell管道"总和"功能?

时间:2016-02-01 20:19:06

标签: haskell pipe

我试图通过编写自己的pipes函数来学习sum包,并且我感到难过。我不想使用Pipes.Prelude中的实用程序函数(因为它有sumfold以及其他使其变得微不足道的函数),并且只使用Pipes.Tutorial中描述的信息。 {1}}。本教程没有讨论Proxy的构造函数,但如果我查看sumfold的源代码,它会使用那些构造函数,我想知道是否可以编写我的sum函数不知道这些低级别的细节。

如果有值可用,我很难接受这个函数如何能够继续获取值,然后以某种方式将该值返回给用户。我想类型是:

sum' :: Monad m => Consumer Int m Int

在我看来这可行,因为这个函数可以消耗值,直到没有更多,然后返回最后的总和。我会这样用:

mysum <- runEffect $ inputs >-> sum'

但是,Pipes.Prelude中的函数具有以下签名:

sum :: (Monad m, Num a) => Producer a m () -> m a

所以我猜这是我的第一个障碍。为什么sum函数将Producer作为参数而不是使用>->进行连接?

仅供参考我在danidiaz回答后得到以下结论:

sum' = go 0
  where
    go n p = next p >>= \x -> case x of
      Left _        -> return n
      Right (_, p') -> go (n + 1) p'

1 个答案:

答案 0 :(得分:5)

实际上,{p> Consumers实际上非常有限。他们无法检测到输入结束(pipes-parse使用不同的技术),当管道的其他部分停止时(例如Producer上游) < / em> part是必须为管道提供结果值的部分。因此将总和放在Consumer的返回值中一般不会起作用。

一些替代方案是:

  • 实现直接处理Producer内部的函数,或者使用next之类的辅助函数。有这种类型的适配器可以将Producer数据提供给更聪明的&#34;消费者,例如foldl包中的Fold

  • 继续使用Consumer,但不是将总和放在Consumer的返回值中,而是使用WriterT作为Sum Int的基本monad } monoid as accumulator。这样,即使Producer先停止,您仍然可以运行编写器来到累加器。但是,此解决方案可能效率较低。

WriterT方法的示例代码:

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