使用管道库,我想编写一个程序来从某个源读取数据并单独累积它(例如,使用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
中的折叠是否有意义围绕严格{{1}构建而不是WriterP
?目前感觉WriterT
中的代理变换器是二等公民,但是缺少许多使pipes
变得如此方便的组合器。
答案 0 :(得分:6)
我正在添加一个新的答案,因为我已经在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
很抱歉第一次得错答案!
答案 1 :(得分:4)
请记住,代理转换器在本地运行,而基本monad在全局运行。这意味着WriterP
每个代理都维护自己的累加器,并且首先终止的代理确定哪个累加器返回。累加器返回0
的原因是因为你的枚举管道首先返回而且它没有累积任何东西。
WriterP
折叠是严格的,因为我可以控制该类型(而不是transformers
中的那个)。我从未打算将它作为代理前奏中的懒惰折叠的严格替代品。使用的正确严格替代方法是foldlD'
。
请注意,foldlD' mappend
基本上是一个严格的Writer
折叠,特别是如果您使用State
作为初始状态运行基础mempty
monad。
在您的情况下,您可以使用以下方式更轻松地完成:
main = do
let source = enumFromToS (0::Int) 5
a <- (`runStateT` 0) $ runProxy $ source >-> foldlD' (+)
print a
这将严格折叠输入。