2011-10-22 6 views

risposta

82

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.

+5

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. –

+5

@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

+3

@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

4

Con Parsec il vantaggio dell'utilizzo di Applicative è proprio lo stile. Monad ha il vantaggio che è più potente: puoi implementare parser sensibili al contesto.

L'analisi UU di Doaitse Swierstra è più efficiente se utilizzata solo in modo applicativo.

+0

ISTR che formalmente, perché Haskell consente infinite grammatiche, monade non effettivamente aumentare il numero di lingue riconoscibili. – luqui

+3

@luqui Sono curioso del tuo commento. Ecco una lingua. L'alfabeto è Haskell 'String's, e la lingua è l'insieme di parole in cui tutte le lettere sono uguali. Questo è facile come un parser monadico: 'opzione [] (anyToken >> = many. ExactToken)' (dove qui 'anyToken' e' exactToken' non sono in realtà una parte della libreria Parsec, ma probabilmente dovrebbero essere; chiedimi se non sei sicuro di quello che fanno). Come apparirebbe il parser applicativo corrispondente? –

+2

@stephen, puoi dare un riferimento per i parser sensibili al contesto? Sono curioso di sapere qual è il potere esatto dei parser monadici e applicativi. – sdcvvc

2

Le Monade sono strettamente un'astrazione più funzionale di rispetto alle Richieste. Si potrebbe scrivere

instance (Monad m) => Applicative m where 
    pure = return 
    (<*>) = ap 

ma non c'è modo di scrivere

instance (Applicative a) => Monad a where 
    return = pure 
    (>>=) = ??? 

Quindi sì, è essenzialmente una questione di stile . Immagino che se usi return e ap, ci dovrebbe essere nessuna perdita di prestazioni sull'uso di pure e <*>. Poiché Applicative è un'interfaccia strettamente più piccola di Monad, ciò significa che a volte lo <*> può essere più ottimizzato di ap. (Ma con le intelligenti regole di riscrittura di GHC, spesso si ottengono le stesse ottimizzazioni a prescindere.)

L'analisi monodica è in corso?

Poiché le Monade sono un sottoinsieme di Applicativi, concluderei che l'analisi applicativa è un sottoinsieme di analisi monadica.

+0

Intendevi dire che le Monade sono una _superset_ di Applicativi? – Guildenstern

+1

@Guildenstern Le operazioni monadiche sono un superset delle operazioni Applicative. Ma detto in un altro modo: i tipi hanno un'istanza di 'Monad' sono un sottoinsieme di tipi che hanno un'istanza di' Applicativo'. Quando si parla di "Monads" e "Applicativi", di solito ci si riferisce ai tipi, non alle operazioni. –

+0

"Immagino che se utilizzi' return' e 'ap', non ci dovrebbero essere perdite di prestazioni rispetto all'uso di' pure' e '<*>'. " IIRC che non è il caso. Ci sono un sacco di situazioni (i parser sono solo uno di loro) dove '<*>' supera le prestazioni di 'ap'. – semicolon

10

Il motivo principale che posso vedere preferire i parser applicativi rispetto ai parser monadici è lo stesso del motivo principale per preferire il codice applicativo rispetto al codice monadico in qualsiasi contesto: essendo meno potenti, gli applicativi sono più semplici da usare.

Questa è un'istanza di un più generale affermazione di ingegneria: utilizza lo strumento più semplice per eseguire il lavoro. Non utilizzare un carrello elevatore quando si eseguirà un carrello. Non utilizzare una sega da banco per tagliare i tagliandi. Non scrivere codice in IO quando potrebbe essere puro. Mantienilo semplice.

Ma a volte, è necessario la potenza aggiuntiva di Monad. Un segno sicuro di ciò è quando è necessario cambiare il corso del calcolo in base a ciò che è stato calcolato finora. In termini di analisi, ciò significa determinare come analizzare ciò che viene dopo in base a ciò che è stato analizzato finora; in altre parole, puoi costruire grammatiche sensibili al contesto in questo modo.

+1

No, "usa lo strumento più semplice" può sembrare una buona regola empirica, ma in realtà non lo è. Ad esempio, usiamo il computer per scrivere lettere, tuttavia il computer su un foglio di carta è qualcosa di simile a una sega da tavolo rispetto a un paio di forbici. –

+0

Voglio dire, ci sono sempre aspetti positivi e negativi per ogni scelta, ma la semplice semplicità è una cattiva base per una scelta. * Specialmente * quando decidi se usare Haskell. : D –

+2

Sì, hai ragione. Sarebbe meglio dire qualcosa del tipo: "lo strumento giusto è quello che è massimamente efficiente pur essendo minimamente complesso". Ciò che manca nella mia descrizione è la parte relativa all'efficienza: si desidera uno strumento sufficientemente potente non solo per svolgere il lavoro, ma per rendere il lavoro il più semplice possibile. Ma allo stesso tempo non si desidera uno strumento che abbia un sacco di campane e fischietti non applicabili al compito in questione, poiché questi molto probabilmente aumentano la complessità del suo funzionamento senza alcun beneficio. –

11

Se un parser è puramente applicativo, è possibile analizzarne la struttura e "ottimizzarlo" prima di eseguirlo. Se un parser è monadico, è fondamentalmente un programma completo di Turing e l'esecuzione di quasi tutte le analisi interessanti è equivalente a risolvere il problema di interruzione (cioè impossibile).

Oh, e sì, c'è una differenza stilistica troppo ...

+1

La differenza tra Applicativo e Monade non ha nulla a che vedere con la completezza di Turing. In Haskell, la relativa difficoltà di ottimizzazione delle istanze di Monad è dovuta solo all'errore storico di esporre '(>> =)' da solo nella classe type, rendendo impossibile per le istanze fornire implementazioni più ottimizzate di operatori come 'ap'. La classe Applicative evita questo errore ed espone '<*>' (l'equivalente di 'ap'). –

+0

@PietDelport, penso che il problema è che le rappresentazioni sottostanti dei parser monadici generalmente non sono suscettibili di istanze 'Applicative' ottimizzate, mentre i parser applicativi generalmente non supportano' >> = '. – dfeuer

+3

@PiDelport Ha assolutamente tutto a che fare con la completezza di Turing. '>> =' prende il risultato del parser a sinistra e lo passa a quello a destra, rendendo impossibile analizzare staticamente il parser risultante, dato che ora lo stesso parser è completo. '<*>' e '<$>' non ispezionare l'argomento, quindi mentre l'output del parser è completo, dato che è possibile mettere qualsiasi cosa alla sinistra di '<$>', l'aspetto dell'analisi stesso è limitato e staticamente analizzabile. – semicolon