如何用管道检测输入结束

时间:2014-12-29 16:31:34

标签: haskell haskell-pipes

我正在尝试从一个管道中读取一组最多50个项目,并在一次IO操作中处理它们。 (这个用例就是我试图将数据插入到数据库中,我想在一个事务中执行整个批处理,因为它的效率要高得多)。这是我到目前为止的简化版本:

type ExampleType = Int

doSomething :: [ExampleType] -> IO ()
doSomething = undefined

inGroupsOf50 :: Monad m => Producer ExampleType m () -> m ()
inGroupsOf50 input =
    runEffect $ input >-> loop
        where loop = do entries <- replicateM 50 await
                    lift $ doSomething entries  --Insert a bunch all in one transaction
                    loop

问题是我所知道的,除非要插入的项目数量除以50,否则我会错过一些。我真正想要的是replicateM 50 await而不是draw,如果输入结束,我可以提供多达50个或更少的项目,但我无法弄清楚如何编写它。

我一直认为pipes-parse可能是正确的库。 producer看起来有一个很有希望的签名......但到目前为止,所有的比特都没有融入我的脑海。我有一个consumer,我正在写一个parser而我并没有真正理解这与{{1}}的概念有什么关系。

1 个答案:

答案 0 :(得分:11)

甚至超过pipes-parse你很可能想看看pipes-group。特别是,我们来看看函数

-- this type is slightly specialized
chunksOf 
  :: Monad m => 
     Int -> 
     Lens' (Producer a m x) (FreeT (Producer a m) m x)

Lens'位可能很吓人,但可以很快消除:它声明我们可以将Producer a m x转换为FreeT (Producer a m) m x [0]

import Control.Lens (view)

chunkIt :: Monad m => Int -> Producer a m x -> FreeT (Producer a m) m x
chunkIt n = view (chunksOf n)

所以现在我们必须弄清楚如何处理FreeT位。特别是,我们想要深入了解free包并提取函数iterT

iterT
  :: (Functor f, Monad m) => 
     (f (m a) -> m a) -> 
     (FreeT f m a -> m a)

这个函数iterT让我们一次“消耗”FreeT一个“步骤”。为了理解这一点,我们首先将iterT的类型专门化为f替换Producer a m

runChunk :: Monad m =>
            (Producer a m (m x)       -> m x) ->
            (FreeT (Producer a m) m x -> m x)
runChunk = iterT

特别是,runChunk可以“运行”FreeTProducerProducer a m (m x)只要我们告诉它如何将m转换为runChunk }-行动。 这个可能会开始变得更加熟悉。当我们定义Producer的第一个参数时,我们只需要执行一个m x,在这种情况下,它将只有选定数量的元素。

但是有效的回报值Producer是怎么回事?这是“延续”,例如当前正在编写的那些块之后的所有块。所以,举个例子,我们假设我们有Char main :: IO () main = flip runChunk (chunkIt 3 input) $ \p -> _ 个,我们想在3个字符后打印和换行

_

此时IO ()洞的类型为p的{​​{1}}类型为p :: Producer Char IO (IO ())。我们可以使用for来使用此管道,收集它的返回类型(再次是延续),发出换行符,然后运行延续。

input :: Monad m => Producer Char m ()
input = each "abcdefghijklmnopqrstuvwxyz"

main :: IO ()
main = flip runChunk (chunkIt 3 input) $ \p -> do
  cont <- runEffect $ for p (lift . putChar)
  putChar '\n'
  cont

这完全符合预期

λ> main
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz

要清楚,虽然我做了一些阐述,但是一旦你看到所有部分如何组合在一起,这是相当简单的代码。以下是整个列表:

input :: Monad m => Producer Char m ()
input = each "abcdefghijklmnopqrstuvwxyz"

main :: IO ()
main = flip iterT (input ^. chunksOf 3) $ \p -> do
  cont <- runEffect $ for p $ \c -> do
    lift (putChar c)
  putChar '\n'
  cont

[0]还有点多,但现在已足够了。