Pregunta

Quería, solo para aprender un poco sobre iterate, reimplegar un analizador simple que hice, usando datos. Iteratee y data.attoparsec.iteratee. Sin embargo, estoy bastante perplejo. A continuación tengo un ejemplo simple que puede analizar una línea desde un archivo. Mi analizador lee una línea a la vez, por lo que necesito una forma de alimentar las líneas al iteradoe hasta que esté listo. He leído todo lo que he encontrado buscando en Google esto, pero gran parte del material sobre iterado/enumeradores está bastante avanzado. Esta es la parte del código que importa:

-- There are more imports above.
import Data.Attoparsec.Iteratee
import Data.Iteratee (joinI, run)
import Data.Iteratee.IO (defaultBufSize, enumFile)

line :: Parser ByteString -- left the implementation out (it doesn't check for 
                             new line)

iter = parserToIteratee line

main = do
    p <- liftM head getArgs
    i <- enumFile defaultBufSize p $ iter
    i' <- run i
    print i'

Este ejemplo analizará e imprimirá una línea desde un archivo con múltiples líneas. El script original mapeó el analizador en una lista de bytestrings. Así que me gustaría hacer lo mismo aquí. encontré enumLinesEn iteradoe, pero no puedo por mi vida descubrir cómo usarlo. ¿Quizás malinterpré su propósito?

¿Fue útil?

Solución

Dado que su analizador trabaja en una línea a la vez, ni siquiera necesita usar AttoParsec-iteratee. Escribiría esto como:

import Data.Iteratee as I
import Data.Iteratee.Char
import Data.Attoparsec as A

parser :: Parser ParseOutput
type POut = Either String ParseOutput

processLines :: Iteratee ByteString IO [POut]
processLines = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) stream2list

La clave para comprender esto es el "enumeratee", que es solo el término iteradoe para un convertidor de flujo. Se necesita un procesador de transmisión (iteratee) de un tipo de flujo y lo convierte para que funcione con otra transmisión. Ambas cosas enumLinesBS y mapStream son enumerados.

Para mapear su analizador a través de múltiples líneas, mapStream es suficiente:

i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list

Los iterados anidados solo significan que esto convierte una corriente de [ByteString] a una corriente de [POut], y cuando se ejecuta el ITeratee final (Stream2List), devuelve esa transmisión como [POut]. Así que ahora solo necesitas el equivalente de iteradoe de lines para crear ese flujo de [ByteString], Que es que enumLinesBS lo hace:

i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
i2 = enumLinesBS $ mapStream f stream2list

Pero esta función es bastante difícil de manejar debido a toda la anidación. Lo que realmente queremos es una forma de obtener la salida directamente entre los convertidores de la corriente, y al final simplifica todo a un solo iteratee. Para hacer esto usamos joinI, (><>), y (><>):

e1 :: Iteratee [POut] IO a -> Iteratee ByteString IO (Iteratee [POut] IO a)
e1 = enumLinesBS ><> mapStream (A.parseOnly parser)

i' :: Iteratee ByteString IO [POut]
i' = joinI $ e1 stream2list

que es equivalente a cómo lo escribí arriba, con e1 Entrada.

Sin embargo, todavía queda un elemento importante. Esta función simplemente devuelve los resultados de análisis en una lista. Por lo general, le gustaría hacer algo más, como combinar los resultados con un pliegue.

editar: Data.Iteratee.ListLike.mapM_ a menudo es útil para crear consumidores. En ese punto, cada elemento de la secuencia es un resultado de análisis, por lo que si desea imprimirlos, puede usar

consumeParse :: Iteratee [POut] IO ()
consumeParse = I.mapM_ (either (\e -> return ()) print)

processLines2 :: Iteratee ByteString IO ()
processLines2 = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) consumeParse

Esto imprimirá solo los analizados exitosos. También puede informar fácilmente errores a Stderr o manejarlos de otras maneras.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top