monadic动作的懒惰输出

Yur*_*ras 5 haskell lazy-io

我有下一个monad变换器:

newtype Pdf' m a = Pdf' {
  unPdf' :: StateT St (Iteratee ByteString m) a
  }
type Pdf m = ErrorT String (Pdf' m)
Run Code Online (Sandbox Code Playgroud)

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

我需要实现一个保存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
Run Code Online (Sandbox Code Playgroud)

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

如何确保save函数ByteString在将其返回给调用者之前不会强制执行?

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

import Data.ByteString (ByteString)
save :: Monad m => (ByteString -> Pdf m ()) -> Pdf m ()
Run Code Online (Sandbox Code Playgroud)

有更好的解决方案吗?

Yur*_*ras 0

到目前为止我找到的解决方案是协程 示例:

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
Run Code Online (Sandbox Code Playgroud)

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