2012-06-03 2 views
5

У меня есть следующая монада трансформатор:Ленивый выходе из монадических действий

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() 

Есть ли лучшее решение?

ответ

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 

Это делает ту же работу, обратный вызов, но вызывающий абонент имеет полный контроль над выходным поколения.

0

Чтобы создать это за один проход, вам нужно будет сохранить (возможно, в состоянии), где были записаны ваши косвенные объекты. Таким образом, сохранение должно отслеживать абсолютную позицию байта, поскольку оно работает. Я не рассматривал, подходит ли ваша Pdf-монада для этой задачи. Когда вы доберетесь до конца, вы можете использовать адреса, хранящиеся в состоянии, для создания раздела xref.

Я не думаю, что поможет двухпроходный алгоритм.

Редактировать 6 июня: Возможно, я сейчас понимаю ваше желание. Для очень быстрой генерации документов, например. HTML, есть несколько библиотек на hackage с «пламенем» в названии. Этот метод заключается в том, чтобы избежать использования «mconcat» в ByteString и использовать его на промежуточном «построителе». Основная библиотека для этого, кажется, 'blaze-builder', которая используется в 'blaze-html' и 'blaze-textual'.

+0

Я просто добавил «реализацию» для функции 'save', чтобы устранить проблему. Да, это должен быть 1-проходный алгоритм, но это не проблема. Сама проблема: когда я вызываю 'mconcat' для создания окончательной ленивой' ByteString', у меня уже есть ее в памяти. Предполагая, что очень большой файл PDF, у меня недостаточно памяти. Я хочу хранить только смещения, а не 'ByteString'. Похоже, подход обратного вызова решает проблему, но я думаю, что лучшее решение должно существовать. – Yuras

+0

Странно, но я не получил уведомление о вашем редактировании в 6 июня. Как «blaze-builder» может мне помочь? «Bulder» определенно быстрее, чем 'ByteString', когда вы хотите« mappend », но проблема в использовании памяти, а не в производительности. 'str1',' str2' и т. д. будут уже в памяти (принудительно BS.length) до 'mconcat'. – Yuras

Смежные вопросы