我正在尝试pipes通过编写自己的sum函数来学习包,我感到难过.我想不是从使用的效用函数Pipes.Prelude(因为它有sum和fold等功能,这使得它微不足道),并仅作为说明使用该信息Pipes.Tutorial.本教程没有讨论构造函数Proxy,但如果我查看它的源代码sum并fold使用那些构造函数,我想知道是否有可能在sum不知道这些低级细节的情况下编写我的函数.
只要有值可用,我就无法理解这个函数如何能够继续获取值,然后以某种方式将该值返回给用户.我想类型将是:
sum' :: Monad m => Consumer Int m Int
Run Code Online (Sandbox Code Playgroud)
在我看来这可以工作,因为这个函数可以消耗值,直到没有更多,然后返回最后的总和.我会像这样使用它:
mysum <- runEffect $ inputs >-> sum'
Run Code Online (Sandbox Code Playgroud)
但是,函数in Pipes.Prelude具有以下签名:
sum :: (Monad m, Num a) => Producer a m () -> m a
Run Code Online (Sandbox Code Playgroud)
所以我想这是我的第一个障碍.为什么sum函数Producer作为参数而不是>->用于连接?
仅供参考我在danidiaz回答后得到以下结论:
sum' = go 0
where
go n p = next p >>= \x -> case x of
Left _ -> return n
Right (_, p') -> go (n + 1) p'
Run Code Online (Sandbox Code Playgroud)
Consumers实际上他们能做的事情非常有限.它们无法检测到输入结束(管道解析使用不同的技术),当管道的某些其他部分停止时(例如Producer上游),该部分必须为管道提供结果值.因此,将总和放在返回值Consumer中将不起作用.
一些替代方案是:
实现一个直接处理Producer内部函数的函数,或者可能使用类似的辅助函数next.有这种类型的适配器可以Producer向"更聪明"的消费者提供数据,例如Fold来自foldl包的消费者.
继续使用a Consumer,但不是将sum放在the的返回值中,而是Consumer使用a WriterT作为基数monad,使用Sum Intmonoid作为累加器.这样,即使Producer首先停止,您仍然可以运行编写器来到累加器.但是,此解决方案可能效率较低.
方法的示例代码WriterT:
import Data.Monoid
import Control.Monad
import Control.Monad.Trans.Writer
import Pipes
producer :: Monad m => Producer Int m ()
producer = mapM_ yield [1..10]
summator :: Monad n => Consumer Int (WriterT (Sum Int) n) ()
summator = forever $ await >>= lift . tell . Sum
main :: IO ()
main = do
Sum r <- execWriterT . runEffect $ producer >-> summator
print r
Run Code Online (Sandbox Code Playgroud)