Haskell 如何编写一个向下游发送从上游接收的内容列表的管道?

Haskell 如何编写一个向下游发送从上游接收的内容列表的管道?,haskell,haskell-pipes,Haskell,Haskell Pipes,我很难写出带有此签名的管道: toOneBigList :: (Monad m, Proxy p) => () -> Pipe p a [a] m r 它应该简单地从上游获取所有as,并将它们发送到下游的列表中 我所有的尝试看起来都彻底失败了 有人能给我指出正确的方向吗?我只给出部分答案,也许其他人会有更好的答案 据我所知,标准管道没有检测管道另一部分何时终止的机制。终止的第一条管道将生成管线的最终结果,其他所有管道将被丢弃。因此,如果您有一个永远消耗输入的管道(最终生成一个列表)

我很难写出带有此签名的
管道

toOneBigList :: (Monad m, Proxy p) => () -> Pipe p a [a] m r
它应该简单地从上游获取所有
a
s,并将它们发送到下游的列表中

我所有的尝试看起来都彻底失败了


有人能给我指出正确的方向吗?

我只给出部分答案,也许其他人会有更好的答案

据我所知,标准管道没有检测管道另一部分何时终止的机制。终止的第一条管道将生成管线的最终结果,其他所有管道将被丢弃。因此,如果您有一个永远消耗输入的管道(最终生成一个列表),那么当其上游完成时,它将没有机会执行操作并生成输出。(这是有意的,因此上游和下游部分彼此都是双重的。)也许这可以在管道顶部的某个图书馆大楼中解决

情况与我们不同。它有一个函数,将所有输入合并到一个列表中,并返回(而不是输出)它。编写一个类似于您所需的函数并在末尾输出列表并不困难:

import Data.Conduit

combine :: (Monad m) => Conduit a m [a]
combine = loop []
  where
    loop xs = await >>= maybe (yield $ reverse xs) (loop . (: xs))

有两种基于
管道的解决方案,我将让您选择您喜欢的解决方案

注意:不清楚为什么要在下游接口上输出列表,而不是直接返回

导管样式 第一个方案非常接近基于
导管的解决方案
,它使用了即将推出的
管道pase
,它基本上是完整的,只需要文档。您可以在Github上找到

使用
管道解析
,解决方案与Petr给出的
管道
解决方案相同:

import Control.Proxy
import Control.Proxy.Parse

combine
    :: (Monad m, Proxy p)
    => () -> Pipe (StateP [Maybe a] p) (Maybe a) [a] m ()
combine () = loop []
  where
    loop as = do
        ma <- draw
        case ma of
            Nothing -> respond (reverse as)
            Just a  -> loop (a:as)
经典管式 第二种选择比较简单。如果要折叠给定管道,可以直接使用
WriterP

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

foldIt
  :: (Monad m, Proxy p) =>
     (() -> Pipe p a b m ()) -> () -> Pipe p a [b] m ()
foldIt p () = runIdentityP $ do
    r <- execWriterK (liftP . p >-> toListD >-> unitU) ()
    respond r
如果
p
的代理类型是完全多态的,
liftP
甚至可能没有必要。我只把它列为预防措施

奖金方案
pipes parse
没有提供
toOneBigList
的原因是,将结果分组到列表中总是一种管道反模式<代码>管道
有几个很好的特性,即使您试图生成多个列表,也可以不必将输入分组到列表中。例如,使用
respond
composition,您可以让代理生成它将要遍历的流的子集,然后注入使用该子集的处理程序:

example :: (Monad m, Proxy p) => () -> Pipe p a (() -> Pipe p a a m ()) m r
example () = runIdentityP $ forever $ do
    respond $ \() -> runIdentityP $ replicateM_ 3 $ request () >>= respond

printIt :: (Proxy p, Show a) => () -> Pipe p a a IO r
printIt () = runIdentityP $ do
    lift $ putStrLn "Here we go!"
    printD ()

useIt :: (Proxy p, Show a) => () -> Pipe p a a IO r
useIt = example />/ (\p -> (p >-> printIt) ())
下面是一个如何使用它的示例:

>>> runProxy $ enumFromToS 1 10 >-> useIt
Here we go!
1
2
3
Here we go!
4
5
6
Here we go!
7
8
9
Here we go!
10

这意味着您永远不需要将单个元素带入内存,即使您需要对元素进行分组。

+1谢谢您的回答。我仍然在等待一个基于管道的。。。除非有人知道这样的管道类型实际上是错误的。foldIt是我要找的。我知道使用列表是一种反模式,但我需要构建它以将其存储在二进制文件中。整个问题是:获取一个大文件(>20000)列表(>20Mb),从每个文件中计算一些统计数据(每个文件10倍),然后将这些结果存储在一个二进制文件中。尽管如此,即使使用管道和foldIt,该程序的内存也会耗尽。我在其他地方做错了…@GiacomoTesio这是因为你正在将文件列表加载到内存中,这就是导致泄漏的原因。我正在开发一个
pipes目录
,它将为您流式处理目录列表,以避免这个常见问题。非常感谢。但我认为这些文件名不是问题所在。我添加了一行
lift$putStrLn$nameOf$binarydeceddsample
,所有文件实际上都列在输出中。事实上,这个过程消耗了大量的文件名内存(2Gb)。@GiacomoTesio第二个罪魁祸首是在序列化列表之前将其加载到内存中。您可以在常量内存中序列化列表(我知道,因为我已经这样做了)。在不浪费大量空间的情况下使其可反序列化的诀窍是将其增量序列化为最大大小的块(即1000个元素),然后用其实际大小作为每个块的前缀。这意味着您永远不会将超过固定数量的元素带入内存。这是一个很好的技巧。但我将转向基于行的格式(CSV、Show或JSON)。这样我就可以一次写一行了。至少,这应该更容易。
example :: (Monad m, Proxy p) => () -> Pipe p a (() -> Pipe p a a m ()) m r
example () = runIdentityP $ forever $ do
    respond $ \() -> runIdentityP $ replicateM_ 3 $ request () >>= respond

printIt :: (Proxy p, Show a) => () -> Pipe p a a IO r
printIt () = runIdentityP $ do
    lift $ putStrLn "Here we go!"
    printD ()

useIt :: (Proxy p, Show a) => () -> Pipe p a a IO r
useIt = example />/ (\p -> (p >-> printIt) ())
>>> runProxy $ enumFromToS 1 10 >-> useIt
Here we go!
1
2
3
Here we go!
4
5
6
Here we go!
7
8
9
Here we go!
10