2011-07-10 16 views
19

Estoy tratando de entender cómo usar la biblioteca iteratee con Haskell. Todos los artículos que he visto hasta ahora parecen enfocarse en construir una intuición sobre cómo se pueden construir iteratees, lo cual es útil, pero ahora que quiero descender y realmente usarlos, me siento un poco en el mar. Mirar el código fuente de iteratees ha sido de valor limitado para mí.Haskell iteratee: simple ejemplo de stripping trailing whitespace

Digamos que tengo esta función que recorta las espacios en blanco de una línea:

import Data.ByteString.Char8 

rstrip :: ByteString -> ByteString 
rstrip = fst . spanEnd isSpace 

Lo que me gustaría hacer es: convertir esto en un iteratee, leer un archivo y escribirlo en otro lugar con el espacio en blanco al final eliminado de cada línea. ¿Cómo voy a estructurar eso con iteratees? Veo que hay una función enumLinesBS en Data.Iteratee.Char que podría sondear en esto, pero no sé si debería usar mapChunks o convStream o cómo reempaquetar la función anterior en un iteratee.

Respuesta

16

Si lo que desea es código, es la siguiente:

procFile' iFile oFile = fileDriver (joinI $ 
    enumLinesBS ><> 
    mapChunks (map rstrip) $ 
    I.mapM_ (B.appendFile oFile)) 
    iFile 

Comentario:

Este es un proceso de tres etapas: primero a transformar la corriente en bruto en una corriente de líneas, a continuación, aplicar su Funciona para convertir ese flujo de líneas, y finalmente usted consume el flujo. Como rstrip se encuentra en la etapa intermedia, creará un transformador de flujo (Enumeratee).

Puede usar mapChunks o convStream, pero mapChunks es más simple. La diferencia es que mapChunks no le permite cruzar los límites de los fragmentos, mientras que convStream es más general. Prefiero convStream porque no expone ninguna de las implementaciones subyacentes, pero si mapChunks es suficiente, el código resultante suele ser más corto.

rstripE :: Monad m => Enumeratee [ByteString] [ByteString] m a 
rstripE = mapChunks (map rstrip) 

Nota extra map en rstripE. El flujo externo (que es la entrada a rstrip) tiene el tipo [ByteString], por lo que necesitamos asignarle rstrip.

Para la comparación, esto es lo que se vería si se aplican con convStream:

rstripE' :: Enumeratee [ByteString] [ByteString] m a 
rstripE' = convStream $ do 
    mLine <- I.peek 
    maybe (return B.empty) (\line -> I.drop 1 >> return (rstrip line)) mLine 

Esto es más larga, y es menos eficiente, ya que sólo se aplicará la función rstrip a una línea a la vez, incluso aunque hay más líneas disponibles. Es posible trabajar en todo el trozo disponibles en la actualidad, que está más cerca de la versión mapChunks:

rstripE'2 :: Enumeratee [ByteString] [ByteString] m a 
rstripE'2 = convStream (liftM (map rstrip) getChunk) 

De todos modos, con el enumeratee despojar disponible, es fácil compuesta con el enumLinesBS enumeratee:

enumStripLines :: Monad m => Enumeratee ByteString [ByteString] m a 
enumStripLines = enumLinesBS ><> rstripE 

El operador de composición ><> sigue el mismo orden que el operador de flecha >>>. enumLinesBS divide el flujo en líneas, luego rstripE las tiras.Ahora sólo tiene que añadir un consumidor (que es un iteratee normal), y ya está:

writer :: FilePath -> Iteratee [ByteString] IO() 
writer fp = I.mapM_ (B.appendFile fp) 

processFile iFile oFile = 
    enumFile defaultBufSize iFile (joinI $ enumStripLines $ writer oFile) >>= run 

Los fileDriver funciones son atajos para simplemente enumerar más de un archivo y ejecutar la desgracia, el orden de los argumentos que resulta iteratee (se cambia de enumFile):

procFile2 iFile oFile = fileDriver (joinI $ enumStripLines $ writer oFile) iFile 

Adición: he aquí una situación en la que se necesita la potencia extra de convStream. Supongamos que quiere concatenar cada 2 líneas en una. No puede usar mapChunks. Considere cuando el fragmento es un elemento singleton, [bytestring]. mapChunks no proporciona ninguna forma de acceder al siguiente fragmento, por lo que no hay nada más que concatenar con esto. Con convStream sin embargo, es muy sencillo:

concatPairs = convStream $ do 
    line1 <- I.head 
    line2 <- I.head 
    return $ line1 `B.append` line2 

esto se ve aún mejor en el estilo aplicativo,

convStream $ B.append <$> I.head <*> I.head 

que se pueda imaginar convStream consumir como continuamente una porción de la corriente con el iteratee proporcionado, a continuación, enviar el versión transformada para el consumidor interno. A veces, incluso esto no es lo suficientemente general, ya que se llama a la misma iteración en cada paso. En ese caso, puede usar unfoldConvStream para pasar el estado entre iteraciones sucesivas.

convStream y unfoldConvStream también permiten acciones monádicas, ya que el iteratee de procesamiento de flujo es un transformador de mónada.

+0

John, ¡gracias por esta respuesta extremadamente detallada! Esto es exactamente lo que necesitaba. –

+0

Dos notas pequeñas: el tipo de rstripE necesita un calificador de clase de tipo (Monad m) =>, y mi función rstrip necesita pegar una nueva línea en el extremo para integrar con enumLinesBS. De lo contrario, funciona como un encanto! –

+0

Gracias por señalar esto, he agregado el contexto de clase de tipo. –