monadic动作的懒惰输出

时间:2012-06-03 13:46:22

标签: haskell lazy-io

我有下一个monad变压器:

newtype Pdf' m a = Pdf' {
  unPdf' :: StateT St (Iteratee ByteString m) a
  }
type Pdf m = ErrorT String (Pdf' m)

基本上,它使用读取和处理pdf文档的底层Iteratee(需要随机访问源,因此它不会一直将文档保存在内存中)。

我需要实现一个保存pdf文档的函数,我希望它是懒惰的,应该可以将文档保存在常量内存中。

我可以制作懒惰的ByteString

import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as BS
save :: Monad m => Pdf m ByteString
save = do
  -- actually it is a loop
  str1 <- serializeTheFirstObject
  storeOffsetForTheFirstObject (BS.length str1)
  str2 <- serializeTheSecondObject
  storeOffsetForTheSecondObject (BS.length str2)
  ...
  strn <- serializeTheNthObject
  storeOffsetForTheNthObject (BS.length strn)
  table <- dumpRefTable
  return mconcat [str1, str2, ..., strn] `mappend` table

但实际输出可能取决于之前的输出。 (详细信息:pdf文档包含所谓的“引用表”,文档中每个对象的绝对偏移量都以字节为单位。这绝对取决于ByteString pdf对象序列化的长度。)

如何确保save函数在将其返回给调用方之前不会强制整个ByteString

将回调作为参数更好并在每次输出内容时调用它吗?

import Data.ByteString (ByteString)
save :: Monad m => (ByteString -> Pdf m ()) -> Pdf m ()

有更好的解决方案吗?

2 个答案:

答案 0 :(得分:0)

要在一次传递中构建它,您将需要存储(可能在状态中)已写入间接对象的位置。所以保存需要跟踪绝对字节位置,因为它工作 - 我没有考虑你的Pdf monad是否适合这个任务。当你到达最后,你可以使用状态中存储的地址来创建外部参照部分。

我不认为双程算法会有所帮助。

编辑6月6日:也许我现在更了解你的愿望。对于非常快速的文档生成,例如HTML,hackage上有几个库,其中包含&#34; blaze&#34;在名字里。该技术是为了避免使用&mconcat&#39;在ByteString上并使用中间的构建器&#39;类型。这个核心库似乎是'blaze-builder',用于&#39; blaze-html&#39;和&#39; blaze-textual&#39;。

答案 1 :(得分:0)

我到目前为止找到的解决方案是Coroutine 例如:

proc :: Int -> Coroutine (Yield String) IO ()
proc 0 = return ()
proc i = do
  suspend $ Yield "Hello World\n" (proc $ i - 1)

main :: IO ()
main = do
  go (proc 10)
  where
  go cr = do
    r <- resume cr
    case r of
      Right () -> return ()
      Left (Yield str cont) -> do
        putStr str
        go cont

它与回调的工作方式相同,但调用者可以完全控制输出生成。