Ленивый выход из монадического действия
У меня есть следующий монадный трансформатор:
newtype Pdf' m a = Pdf' {
unPdf' :: StateT St (Iteratee ByteString m) a
}
type Pdf m = ErrorT String (Pdf' m)
В основном, он использует основные 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
Но фактический результат может зависеть от предыдущего результата. (Подробности: PDF-документ содержит так называемую "справочную таблицу" с абсолютным смещением в байтах каждого объекта внутри документа. Это определенно зависит от длины ByteString
pdf объект сериализуется в.)
Как обеспечить это save
функция не заставит весь ByteString
перед возвратом звонящему?
Лучше ли принимать обратный вызов в качестве аргумента и вызывать его каждый раз, когда мне есть что выводить?
import Data.ByteString (ByteString)
save :: Monad m => (ByteString -> Pdf m ()) -> Pdf m ()
Есть ли лучшее решение?
2 ответа
Решение, которое я нашел до сих пор, - пример 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
Он выполняет ту же работу, что и обратный вызов, но вызывающая сторона имеет полный контроль над генерацией вывода.
Чтобы построить это за один проход, вам нужно будет хранить (возможно, в состоянии), где были написаны ваши косвенные объекты. Таким образом, сохранение должно отслеживать абсолютную позицию байта, как оно работает - я не думал, подходит ли ваша монада Pdf для этой задачи. Когда вы дойдете до конца, вы можете использовать адреса, хранящиеся в состоянии, для создания раздела внешних ссылок.
Я не думаю, что двухпроходный алгоритм поможет.
Редактировать 6 июня: Возможно, теперь я понимаю ваше желание лучше. Для очень быстрой генерации документов, например HTML, есть несколько библиотек с хакерскими именами в названии. Техника заключается в том, чтобы избежать использования mconcat в ByteString и использовать промежуточный тип builder. Основной библиотекой для этого является "blaze-builder", который используется в "blaze-html" и "blaze-textual".