如何编写Haskell Pipes的“sum”函数?

8
我正在尝试通过编写自己的sum函数来学习pipes包,但我遇到了困难。我不想使用来自Pipes.Prelude的实用函数(因为它具有sumfold等使其变得微不足道的其他函数),而只使用Pipes.Tutorial中描述的信息。教程没有讨论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'

管道中的 sum 接受一个生成数字并在单子操作中将它们相加的东西...如果你仔细想想,这个想法与你的想法非常相似 - 此外,该软件包的设计使您不必关心细节,而是使用 fold 和提供的其他基元 ;) - Random Dev
1个回答

5

消费者的能力是非常有限的。它们无法检测到输入结束(pipes-parse使用了不同的技术来实现)并且当管道中的其他部分停止运行(例如上游的生产者), 那个部分必须为管道提供结果值。因此,通常情况下将总和放在Consumer的返回值中是行不通的。

一些替代方法包括:

  • 实现一个直接处理Producer内部的函数,或者可能使用像next这样的辅助函数。有这种类型的适配器可以将Producer数据提供给“更加智能”的消费者,例如来自foldl包的Fold

  • 继续使用Consumer,但是不要将总和放在Consumer的返回值中,而是使用WriterT作为基本单元,使用Sum Int作为累加器。这样,即使Producer先停止,您仍然可以运行writer来获取累加器。不过,这种解决方案可能效率较低。

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

无法检测到输入结束符是我遗漏的。非常感谢。 - user1002430

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接