我有下一个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 ()
有更好的解决方案吗?
答案 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
它与回调的工作方式相同,但调用者可以完全控制输出生成。