Domanda

Sto cercando di utilizzare MonadError insieme a Parsec.Ho creato il seguente frammento di codice:

f5 = do
    char 'a'
    throwError "SomeError"

f6 = f5 `catchError` (\e -> unexpected $ "Got the error: " ++ e)

ret = runErrorT (runParserT f6 () "stdin" "a")

Tuttavia, ret È Left "SomeError", sembra che catchError non abbia alcun effetto.Qual è il modo giusto di utilizzare MonadError qui?

Preferirei utilizzare MonadError rispetto alla gestione degli errori di Parsec, come ad esempio quando ho:

try (many1 parser1) <|> parser2

Se il parser1 fallisce qui, il parser2 continuerà, ma mi piacerebbe avere un'eccezione che interrompa completamente l'analisi.

È stato utile?

Soluzione

Ho l'impressione che tu stia cercando di coinvolgere MonadError per la ragione sbagliata.

Nel try (many1 parser1) <|> parser2, il comportamento che stai cercando di evitare deriva dall'uso di try E <|> - se non ti piace, usa combinatori diversi.Forse un'espressione come (many1 parser1) >> parser2 funzionerebbe meglio per te?(Questo scarta i risultati da (many1 parser1);potresti ovviamente usare >>= e combinare i risultati di (many1 parser1) con quelli di parser2.)


(Nota:Al di sotto di questo punto, non esiste una soluzione veramente valida al problema in questione, solo alcune riflessioni sul motivo per cui alcune cose probabilmente non funzioneranno...Spero che questo possa essere (in qualche modo) illuminante, ma non aspettatevi troppo.)

Un esame più attento dell'interazione ParsecT / MonadError.Temo che sia un po' complicato e non sono ancora sicuro del modo migliore per fare ciò che l'OP vuole fare, ma spero che quanto segue fornisca almeno informazioni sulle ragioni del mancato successo di l'approccio originale.

Innanzitutto, notalo non è corretto dire che Parsec è un'istanza di MonadError.Parsec è la monade prodotta da ParsecT quando la monade interiore è Identità;ParsecT produce istanze di MonadError se e solo se gli viene data una monade interna che è essa stessa un'istanza di MonadError con cui lavorare.Frammenti rilevanti delle interazioni GHCi:

> :i Parsec
type Parsec s u = ParsecT s u Identity
    -- Defined in Text.Parsec.Prim
-- no MonadError instance

instance (MonadError e m) => MonadError e (ParsecT s u m)
  -- Defined in Text.Parsec.Prim
-- this explains why the above is the case
-- (a ParsecT-created monad will only become an instance of MonadError through
-- this instance, unless of course the user provides a custom declaration)

Prossimo, facciamo un esempio funzionante con catchError e ParsecT.Considera questa interazione GHCi:

> (runParserT (char 'a' >> throwError "some error") () "asdf" "a" :: Either String (Either ParseError Char)) `catchError` (\e -> Right . Right $ 'z')
Right (Right 'z')

L'annotazione del tipo appare necessaria (questo mi sembra avere un senso intuitivo, ma non è pertinente alla domanda originale, quindi non cercherò di elaborare).Il tipo dell'intera espressione è determinato da GHC come segue:

Either String (Either ParseError Char)

Quindi, abbiamo un risultato di analisi regolare -- Either ParseError Char - avvolto nel Either String monade al posto del solito Identity monade.Da Either String è un'istanza di MonadError, possiamo usare throwError / catchError, ma il gestore è passato a catchError deve ovviamente produrre un valore del tipo corretto.Temo che non sia molto utile per uscire dalla routine di analisi.

Torniamo al codice di esempio dalla domanda. Questo fa una cosa leggermente diversa.Esaminiamo il tipo di ret come definito nella domanda:

forall (m :: * -> *) a.
(Monad m) =>
m (Either [Char] (Either ParseError a))

(Secondo GHCi...nota che ho dovuto eliminare la restrizione del monomorfismo con {-# LANGUAGE NoMonomorphismRestriction #-} per compilare il codice senza annotazioni di tipo.)

Questo tipo è un suggerimento sulla possibilità di fare qualcosa di divertente ret.Eccoci qui:

> runParserT ret () "asdf" "a"
Right (Left "some error")

Col senno di poi, il conduttore ha dato a catchError produce un valore utilizzando unexpected, quindi ovviamente sarà (utilizzabile come) un parser...E temo di non vedere come trasformarlo in qualcosa di utile per uscire dal processo di analisi.

Altri suggerimenti

Se si sta cercando di eseguire il debug di un parser per risolvere i problemi, è probabilmente più semplice da usare error, Debug.Trace, o roba del genere.

D'altra parte, se avete bisogno di terminare il parsing su alcuni input come parte del vostro programma vero e proprio, ma non sta facendo così a causa di un costrutto try (...) <|>, allora avete un bug nella logica e si dovrebbe fermare e ripensare la grammatica, invece di incidere intorno con la gestione degli errori.

Se si desidera che il parser per terminare su un dato di ingresso per qualche tempo, ma non altri, allora o qualcosa che mancava nel tuo flusso di input (e deve essere aggiunto) o un parser non è la soluzione al vostro problema.

Se si desidera che il parser per recuperare con grazia da errori non fatali e continuare a provare, quando possibile, ma termina con un errore quando non può continuare, allora si ... può prendere in considerazione qualcosa di diverso da Parsec, perché è davvero non progettati per questo. Credo che i supporti di libreria Haskell parser combinatore di Università di Utrecht, che tipo di logica molto più facilmente.

Modifica : Per quanto riguarda la Parsec essendo esso stesso un esempio di MonadError va - sì, e la manipolazione proprio errore sussume tale funzionalità. Quello che stai cercando di fare è di stack di un secondo monade errore sulla parte superiore del Parsec, e probabilmente stai avendo problemi poiché è generalmente difficile da distinguere tra i trasformatori monade che sono "ridondanti" in quel modo. Trattare con più monadi Stato è più notoriamente scomoda, che è il motivo per cui Parsec (uno Stato Monade pure) fornisce la funzionalità di stato di attesa personalizzato.

In altre parole, Parsec essere una monade errore non ti aiuta a tutti, e in effetti è rilevante per lo più nel senso di rendere il vostro problema più difficile.

se avete bisogno di terminare il parsing su alcuni input come parte del vostro programma vero e proprio, ma non sta facendo così a causa di una prova (...) <|> costrutto, allora avete un bug nella logica e si dovrebbe fermare e ripensare la grammatica, invece di incidere intorno con la gestione degli errori.

Se si desidera che il parser per terminare su un dato di ingresso per qualche tempo, ma non altri, allora o qualcosa che mancava nel tuo flusso di input (e deve essere aggiunto) o un parser non è la soluzione al vostro problema.

Questa risposta si basa sul presupposto che le bugie problema nella grammatica. Ma se sto usando la grammatica per alimentare un compilatore, ci sono altri errori che una grammatica non può gestire. Diciamo che un riferimento variabile, a una variabile che non è stato definito. E la lingua è specificato come un unico passaggio, e le variabili vengono valutati come incontrato. Poi, la grammatica è più che bene. L'analisi è più che bene. Ma come risultato di valutare ciò che è stato specificato nella grammatica è verificato un errore, l'attuale "fallire" o "imprevisti" o insufficiente per affrontare questo problema. Sarebbe bello avere un mezzo per interrompere il parsing senza ricorrere a più alto livello di gestione degli errori.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top