La differenza principale tra l'analisi monodica e applicativa riguarda la modalità di gestione della composizione sequenziale. Nel caso di un parser applicativo, usiamo (<*>)
, mentre con una monade usiamo (>>=)
.
(<*>) :: Parser (a -> b) -> Parser a -> Parser b
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
L'approccio monadica è più flessibile, perché permette la grammatica della seconda parte dipendere dal risultato del primo, ma raramente abbiamo bisogno di questo ulteriore flessibilità nella pratica.
Si potrebbe pensare che avere una certa flessibilità extra non possa far male, ma in realtà è possibile. Ci impedisce di fare analisi statiche utili su un parser senza eseguirlo. Ad esempio, supponiamo di voler sapere se un parser può corrispondere o meno alla stringa vuota e quali possono essere i possibili primi caratteri in una corrispondenza. Vogliamo funzioni
empty :: Parser a -> Bool
first :: Parser a -> Set Char
Con un parser applicativo, possiamo facilmente rispondere a questa domanda. (Sto imbrogliando un po 'qui Immaginate di avere un costruttore di dati corrispondente a (<*>)
e (>>=)
nel nostro parser candidato "lingue").
Tuttavia, con un parser monadico non sappiamo quale sia la grammatica della seconda parte senza conoscere l'input.
Consentendo più, siamo in grado di ragionare meno. Questo è simile alla scelta tra i sistemi di tipo dinamico e statico.
Ma qual è il punto di questo? A cosa potremmo usare questa conoscenza statica extra? Bene, possiamo ad esempio usarlo per evitare il backtracking in LL (1) parsing confrontando il prossimo carattere con il set first
di ogni alternativa. Possiamo anche stabilire staticamente se ciò sarebbe ambiguo controllando se i set di first
di due alternative si sovrappongono.
Un altro esempio è che può essere utilizzato per il recupero degli errori, come mostrato nel documento Deterministic, Error-Correcting Combinator Parsers di S. Doaitse Swierstra e Luc Duponcheel.
In genere, tuttavia, la scelta tra analisi parsing e applicativa è già stata effettuata dagli autori della libreria di analisi che si sta utilizzando. Quando una libreria come Parsec espone entrambe le interfacce, la scelta di quale utilizzare è puramente stilistica. In alcuni casi il codice applicativo è più facile da leggere rispetto al codice monadico e talvolta è il contrario.
Aspetta! Avevo pensato la stessa cosa fino ad oggi, quando mi è venuto in mente che il test 'vuoto' può essere applicato anche ai parser monadici. Il motivo è che possiamo ottenere il valore che hai chiamato '???' applicando il parser 'x' sulla stringa vuota. Più in generale, puoi semplicemente inserire la stringa vuota nel parser e vedere cosa succede. Allo stesso modo, l'insieme dei primi caratteri può essere ottenuto almeno in una forma funzionale 'first :: Parser a -> (Char -> Bool)'. Ovviamente, la conversione di quest'ultimo in 'Set Char' comporterebbe un'enumerazione inefficiente di caratteri, ed è qui che i funtori applicativi hanno il vantaggio. –
@HeinrichApfelmus Non è possibile ottenere una risposta a 'empty' in questo modo. O tu * puoi *, ma è come dare una risposta a [http://en.wikipedia.org/wiki/Halting_problem] con "consente di eseguire il programma e vedere se si ferma". – phadej
@hammar: cosa succede se eseguiamo 'let x = pure f <*> y <*> x in x vuota. Se 'empty y' è' False', il calcolo non termina ... solo per sottolineare che non è così semplice. – phadej