4

使用管道库,我想编写一个程序来从某个源读取数据并将其累积(例如,使用Sum)。最简单的方法是,

 import Control.Proxy as 
 import Data.Monoid (Sum)

 main = do
     let source = enumFromToS (0::Int) 5
     a <- runWriterT $ runProxy $ source >-> foldD Sum
     print a

当然,虽然这适用于小源,但由于WriterT累加器的惰性,大输入会导致可怕的堆栈溢出。

值得庆幸的是,它似乎pipes预料到了这一点,为WriterP代理提供了严格的累加器。不幸的是,围绕这个代理的文档非常稀少。经过一番摸索(并简化了问题,而是为每个下游元素累积一个 1),我来到了这个程序,

import Control.Proxy
import Control.Proxy.Trans.Writer
import Data.Monoid (Sum)

main = do
    let source = enumFromToS (0::Int) 5
    a <- runProxy $ runWriterK $ source >-> \x->tell (Sum 1::Sum Int)
    print a

当然,这个程序甚至没有正确执行简化的任务,因为它累积到 1 而不是 6。如果我没记错的话,这种行为可以通过管道在终止之前只读取一个元素这一事实来解释。重复直到输入结束,我想出了以下内容,

import Control.Proxy
import Control.Proxy.Trans.Writer
import Data.Monoid (Sum)

main = do
    let source = enumFromToS (0::Int) 5
    a <- runProxy $ runWriterK $ source >-> fold Sum
    print a

fold :: (Monad m, Proxy p, Monoid w) => (a -> w) -> a' -> WriterP w p a' a a' a m r
fold f = go
  where go x = do a <- request x
                  tell $ f a
                  x' <- respond a
                  go x'

但是,此代码返回的累加器为 0。这是为什么呢?有没有像我fold提供的功能pipes

鉴于 for 的许多用例pipes是处理大型数据集的长时间运行的流程,Control.Proxy.Prelude围绕严格WriterP而不是WriterT. 目前感觉中的代理转换pipes器是二等公民,存在但缺少许多WriterT如此方便的组合器。

4

2 回答 2

7

我正在添加一个新答案,因为我已经在 中解决了这个问题pipes-3.3,我刚刚将它上传到 Hackage。管道背后的理论表明,您所期望的全局行为一直都是正确的行为,WriterP现在全局行为,因此您可以在管道内折叠。

我已经修改了您的示例,以显示您将使用以下方法实现它pipes-3.3

import Control.Proxy
import Control.Proxy.Trans.Writer

main = do
    let source = enumFromToS (0::Int) 5
    a <- runProxy $ execWriterK $ source >-> sumD
    print a

您现在还可以检索管道中折叠的结果。例如,这是完全有效的:

chunksOf :: (Monad m, Proxy p) => Int -> () -> Pipe p a [a] m r
chunksOf n () = runIdentityP $ forever $ do
    -- The unitU discards the values that 'toListD' reforwards
    as <- execWriterK (takeB_ n >-> toListD >-> unitU) ()
    respond as

这是一个示例用法:

>>> runProxy $ getLineS >-> takeWhileD (/= "quit") >-> chunksOf 3 >-> printD
1<Enter>
2<Enter>
3<Enter>
["1","2","3"]
4<Enter>
5<Enter>
6<Enter>
["4","5","6"]
quit

抱歉第一次回答错了!

于 2013-05-06T07:07:43.953 回答
4

请记住,代理转换器在本地运行,而基本单子在全局运行。这意味着让WriterP每个代理维护自己的累加器,并且首先终止的代理确定返回哪个累加器。累加器返回的原因0是因为您的枚举管道首先返回并且它没有累积任何东西。

折叠是严格的WriterP,只是因为我可以控制该类型(而不是 in 中的那个transformers)。我从来没有打算让它成为代理前奏中懒惰折叠的严格替代品。正确的严格替代使用是foldlD'.

请注意,这foldlD' mappend基本上是一个严格的Writer折叠,特别是如果您使用作为初始状态运行基本Statemonad 。mempty

在您的情况下,您可以更轻松地使用:

main = do
    let source = enumFromToS (0::Int) 5
    a <- (`runStateT` 0) $ runProxy $ source >-> foldlD' (+)
    print a

这将严格折叠输入。

于 2013-01-24T19:39:08.103 回答