AttoparSec iterato
-
28-10-2019 - |
Domanda
Volevo solo imparare un po 'su iteratees, reimplement un semplice parser che ho realizzato, usando data.iteee e data.attopArsec.iteratee. Sono praticamente sconcertato però. Di seguito ho un semplice esempio che è in grado di analizzare uno riga da un file. Il mio parser legge una riga alla volta, quindi ho bisogno di un modo per nutrire le linee per iterare fino a quando non è finito. Ho letto tutto ciò che ho trovato a cercare su Google questo, ma molto materiale su Iterati/Enumerators è piuttosto avanzato. Questa è la parte del codice che conta:
-- 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'
Questo esempio analizzerà e stampa una riga da un file con più righe. La sceneggiatura originale ha mappato il parser su un elenco di spettacoli. Quindi vorrei fare la stessa cosa qui. ho trovato enumLines
In Iteratoe, ma non posso per la vita di me capire come usarlo. Forse fraintengo il suo scopo?
Soluzione
Dal momento che il parser funziona su una linea alla volta, non è nemmeno necessario usare AttopArsec-Iteratee. Scriverei questo come:
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 chiave per comprenderlo è il "enumeratee", che è solo il termine iterato per un convertitore di flusso. Ci vuole un processore di flusso (iterato) di un tipo di flusso e lo converte per funzionare con un altro flusso. Tutti e due enumLinesBS
e mapStream
sono elencati.
Per mappare il parser su più righe, mapStream
è sufficiente:
i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list
Gli iterati nidificati significano solo che questo converte un flusso di [ByteString]
a un flusso di [POut]
, e quando viene eseguito l'ultimo iterato (Stream2List), restituisce quel flusso come [POut]
. Quindi ora hai solo bisogno dell'equivalente di iTratee di lines
per creare quel flusso di [ByteString]
, che è cosa enumLinesBS
fa:
i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
i2 = enumLinesBS $ mapStream f stream2list
Ma questa funzione è piuttosto ingombrante da usare a causa di tutta la nidificazione. Ciò che vogliamo veramente è un modo per il tubo di output direttamente tra i convertitori di flusso e alla fine semplificare tutto a un singolo Iterato. Per fare questo che usiamo joinI
, (><>)
, e (><>)
:
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
che equivale a come l'ho scritto sopra, con e1
inlineato.
Rimango ancora un elemento importante. Questa funzione restituisce semplicemente i risultati di analisi in un elenco. In genere vorresti fare qualcos'altro, come combinare i risultati con una piega.
modificare: Data.Iteratee.ListLike.mapM_
è spesso utile per creare consumatori. A quel punto ogni elemento del flusso è un risultato di analisi, quindi se si desidera stamparli puoi usare
consumeParse :: Iteratee [POut] IO ()
consumeParse = I.mapM_ (either (\e -> return ()) print)
processLines2 :: Iteratee ByteString IO ()
processLines2 = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) consumeParse
Questo stamperà solo le analisi di successo. Potresti facilmente segnalare errori a Stderr o gestirli anche in altri modi.