haskell - 用管道的 WriterP 编写一个简单的累加器

标签 haskell haskell-pipes

使用管道库,我想编写一个程序来从某个源读取数据并以幺半方式累积它(例如,使用 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 的函数?

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

最佳答案

我正在添加一个新答案,因为我已经在 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

抱歉第一次答错!

关于haskell - 用管道的 WriterP 编写一个简单的累加器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14507700/

相关文章:

haskell - 为什么aeson-native安装失败?

haskell - 在 SML 中编码 rank-2 多态性等价物

Haskell 快速并发队列

haskell - 如何将 `readfile` 函数的输出变成管道的源?

haskell - 为简单函数管道等效代码

string - haskell 从文件中读取

haskell - 内存和重复 IO monad

haskell - RWST 管道中的空间泄漏

haskell - 错误 "Not a data constructor"的原因是什么?

haskell - 如何检测 Haskell Pipe 中的最后一个 block ?