Haskell 用管道编写一个简单的累加器';书写器
使用pipes库,我想编写一个程序,从某个源读取数据,并进行单向累积(例如,使用Haskell 用管道编写一个简单的累加器';书写器,haskell,haskell-pipes,Haskell,Haskell Pipes,使用pipes库,我想编写一个程序,从某个源读取数据,并进行单向累积(例如,使用Sum)。最简单的方法是 导入控件。代理为 导入数据。幺半群(总和) main=do 让source=enumFromToS(0::Int)5 a->foldD Sum 打印 当然,虽然这适用于小型源,但由于WriterT累加器的惰性,大型输入将导致可怕的堆栈溢出 谢天谢地,pipes似乎预见到了这一点,提供了一个具有严格累加器的WriterP代理。不幸的是,围绕这个代理的文档非常稀少。经过一番探索(并将问题简化为
Sum
)。最简单的方法是
导入控件。代理为
导入数据。幺半群(总和)
main=do
让source=enumFromToS(0::Int)5
a->foldD Sum
打印
当然,虽然这适用于小型源,但由于WriterT
累加器的惰性,大型输入将导致可怕的堆栈溢出
谢天谢地,pipes
似乎预见到了这一点,提供了一个具有严格累加器的WriterP
代理。不幸的是,围绕这个代理的文档非常稀少。经过一番探索(并将问题简化为每个下游元素累积1),我来到了这个程序
import-Control.Proxy
导入控制.Proxy.Trans.Writer
导入数据。幺半群(总和)
main=do
让source=enumFromToS(0::Int)5
a->\x->tell(Sum 1::Sum Int)
打印
当然,这个程序甚至不能正确执行简化任务,因为它累加到1而不是6。如果我没有弄错的话,这种行为可以解释为管道在终止之前只读取一个元素。为了重复直到输入结束,我得出以下结论:
import-Control.Proxy
导入控制.Proxy.Trans.Writer
导入数据。幺半群(总和)
main=do
让source=enumFromToS(0::Int)5
一倍之和
打印
折叠::(单子m,代理p,幺半群w)=>(a->w)->a'->写p a'a'a m r
折叠f=开始
其中go x=do a请记住,代理转换器在本地运行,而基monad在全局运行。这意味着WriterP
让每个代理维护自己的累加器,首先终止的代理决定返回哪个累加器。累加器返回0
的原因是您的枚举管道首先返回,而它没有累计任何内容
WriterP
折叠之所以严格,只是因为我可以控制该类型(而不是transformers
中的类型)。我从来没有打算让它成为《代理前奏曲》中懒洋洋的褶皱的严格替代品。正确的严格替代方法是foldlD'
请注意,foldlD'mappend
基本上是一个严格的Writer
折叠,特别是当您使用mempty
作为初始状态运行基本State
monad时
在您的情况下,您可以更轻松地使用:
main = do
let source = enumFromToS (0::Int) 5
a <- (`runStateT` 0) $ runProxy $ source >-> foldlD' (+)
print a
main=do
让source=enumFromToS(0::Int)5
a->foldlD'(+)
打印
这将严格折叠输入。我添加了一个新的答案,因为我已经在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
import-Control.Proxy
导入控制.Proxy.Trans.Writer
main=do
让source=enumFromToS(0::Int)5
a->sumD
打印
现在还可以检索管道中的折叠结果。例如,这是完全有效的:
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
chunksOf::(Monad m,Proxy p)=>Int->()->Pipe pa[a]m r
chunksofn()=runIdentityP$forever$do
--unitU丢弃“toListD”重新前进的值
as->toListD>->unitU)()
回应为
下面是一个示例用法:
>>> 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
>>runProxy$getLineS>->takeWhileD(/=“quit”)>->chunksof3>->printD
1.
2.
3.
["1","2","3"]
4.
5.
6.
["4","5","6"]
退出
很抱歉第一次答错了 您正试图通过管道
重新实现foldD
和sumD
。查看源代码应该可以澄清您的疑问。@C.A.McCann它没有足够严格地折叠东西。当我有更多的时间时,我计划要求在《变形金刚》中添加一个更严格的版本。@GabrielGonzalez:啊,现在我看看源代码,我明白你的意思了。我想,@C.A.McCann,Control.Monad.Trans.Writer.strict
的“严格性”(或缺乏严格性)在过去曾多次让我绊倒。我的印象是,甚至不可能编写一个严格累加的(例如,强制执行mappend
)Writer
单子,因为您没有任何可以seq
@bgamari:是的。在查看源代码后,我现在意识到“严格”版本本质上是幼稚的实现,而“懒惰”版本添加了无可辩驳的模式,使其更加不严格。感谢更新!我将很快尝试合并此方法。