2012-06-03 2 views
5

J'ai le transformateur suivant monade:sortie Lazy d'une action monadique

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

Fondamentalement, il utilise sous-jacente Iteratee qui lit et traite le document pdf (nécessite la source d'accès aléatoire, de sorte qu'il ne sera pas conserver le document en mémoire tout le temps).

J'ai besoin d'implémenter une fonction qui sauvera document pdf, et je veux que ce soit paresseux, il devrait être possible d'enregistrer le document en mémoire constante.

Je peux produire ByteString paresseux:

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 

Mais la production réelle peut dépendre de la sortie précédente. (Détails:.. Document PDF contient donc appelé « table de référence » avec décalage absolu en octets de chaque objet à l'intérieur du document Cela dépend vraiment de la durée du ByteString objet pdf est sérialisé)

Comment assurer cette fonction save ne sera pas forcer ByteString avant de le renvoyer à l'appelant?

Est-il préférable de prendre le rappel comme argument et l'appeler à chaque fois que j'ai quelque chose à la sortie?

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

Y a-t-il une meilleure solution?

Répondre

0

La solution que je trouve à ce jour est Coroutine Exemple:

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 

Il fait le même travail que le rappel, mais l'appelant a le plein contrôle sur la génération de sortie.

0

Pour construire ce en un seul passage, vous devrez stocker (peut-être dans l'état) où vos objets indirects ont été écrits. La sauvegarde doit donc garder la trace de la position absolue des octets car elle fonctionne. Je n'ai pas examiné si votre monade Pdf est adaptée à cette tâche. Lorsque vous arrivez à la fin, vous pouvez utiliser les adresses stockées dans l'état pour créer la section xref.

Je ne pense pas un algorithme à deux passes vous aidera.

Modifier Juin 6: Peut-être que je comprends mieux votre désir maintenant. Pour la génération très rapide de documents, par ex. HTML, il existe plusieurs bibliothèques sur hackage avec "blaze" dans le nom. La technique consiste à éviter d'utiliser 'mconcat' sur ByteString et à utiliser dans un type 'builder' intermédiaire. La bibliothèque principale pour cela semble être 'blaze-builder', qui est utilisée dans 'blaze-html' et 'blaze-textual'.

+0

Je viens d'ajouter une implémentation pour la fonction 'save' afin de rendre le problème plus clair. Oui, ce devrait être un algorithme à un passage, mais ce n'est pas un problème. Le problème en lui-même: quand j'appelle 'mconcat' pour produire le dernier ByteString paresseux, je l'ai déjà en mémoire. En supposant un très gros fichier pdf, je n'ai pas assez de mémoire pour cela. Je veux stocker seulement des décalages, pas le 'ByteString' lui-même. On dirait que l'approche de rappel résout le problème, mais je pense qu'une meilleure solution devrait exister. – Yuras

+0

Étrange, mais je n'ai pas reçu de notification au sujet de votre modification au 6 juin. Comment 'blaze-builder' peut m'aider? 'Bulder' est définitivement plus rapide que' ByteString' quand vous voulez 'mappend', mais le problème est l'utilisation de la mémoire, pas la performance. 'str1',' str2', etc. seront déjà en mémoire (forcé par BS.length) avant 'mconcat'. – Yuras