2012-06-03 17 views
5

Tengo el siguiente transformador mónada:salida perezoso de la acción monádico

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

Básicamente, se utiliza subyacente Iteratee que lee y procesa el documento pdf (requiere fuente de acceso aleatorio, por lo que no va a mantener el documento en memoria todo el tiempo).

Necesito implementar una función que guardará el documento pdf, y quiero que sea flojo, debería ser posible guardar el documento en la memoria constante.

puedo producir perezoso 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 

Pero salida real puede depender de salida anterior. (Detalles:.. El documento PDF contiene la llamada "tabla de referencia" con absoluta desplazamiento en bytes de cada objeto dentro del documento Definitivamente depende de la duración de ByteString objeto PDF es serializado a)

¿Cómo garantizar que la función save no lo hará fuerza todo ByteString antes de devolverlo a la persona que llama?

¿Es mejor tomar la devolución de llamada como argumento y llamarla cada vez que tengo algo que mostrar?

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

¿Hay alguna solución mejor?

Respuesta

0

La solución que encontré hasta ahora es Coroutine Ejemplo:

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 

Se hace el mismo trabajo que de devolución de llamada, pero la persona que llama tiene un control total sobre la generación de salida.

0

Para construir esto de una sola vez, deberá almacenar (tal vez en el estado) donde se han escrito sus objetos indirectos. Por lo tanto, el guardado necesita hacer un seguimiento de la posición del byte absoluto a medida que funciona: no he considerado si su mónada Pdf es adecuada para esta tarea. Cuando llegue al final, puede usar las direcciones almacenadas en el estado para crear la sección xref.

No creo que sea útil un algoritmo de dos pasos.

Editar 6 de junio: Tal vez entiendo su deseo mejor ahora. Para una generación de documentos muy rápida, p. HTML, hay varias bibliotecas en hackage con "blaze" en el nombre. La técnica consiste en evitar el uso de 'mconcat' en ByteString y utilizarlo en un tipo de 'generador' intermedio. La biblioteca central para esto parece ser 'blaze-builder', que se usa en 'blaze-html' y 'blaze-textual'.

+0

Acabo de agregar una "implementación" para la función 'save' para aclarar el problema. Sí, debería ser un algoritmo de 1 paso, pero no es un problema. El problema en sí: cuando llamo a 'mconcat' para producir el último perezoso' ByteString', ya lo tengo en la memoria. Suponiendo un archivo pdf muy grande, no tengo suficiente memoria para eso. Quiero almacenar solo desplazamientos, no el 'ByteString' en sí. Parece que el enfoque de devolución de llamada resuelve el problema, pero creo que debería existir una mejor solución. – Yuras

+0

Extraño, pero no recibí ninguna notificación sobre su edición el 6 de junio. ¿Cómo '' blaze-builder' puede ayudarme? 'Bulder' es definitivamente más rápido que' ByteString' cuando quieres 'mappend', pero el problema es el uso de la memoria, no el rendimiento. 'str1',' str2', etc. ya estará en la memoria (forzado por BS.length) antes de 'mconcat'. – Yuras

Cuestiones relacionadas