La principale différence entre l'analyse syntaxique monadique et applicative réside dans la manière dont la composition séquentielle est traitée. Dans le cas d'un analyseur applicatif, on utilise (<*>)
alors qu'avec une monade, on utilise (>>=)
.
(<*>) :: Parser (a -> b) -> Parser a -> Parser b
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
L'approche monadique est plus flexible, car elle permet à la grammaire de la deuxième partie de dépendre du résultat de la première, mais nous avons rarement besoin de cette flexibilité supplémentaire en pratique.
Vous pourriez penser qu'avoir une certaine flexibilité supplémentaire ne peut pas nuire, mais en réalité, c'est le cas. Cela nous empêche de faire une analyse statique utile sur un analyseur sans l'exécuter. Par exemple, disons que nous voulons savoir si un analyseur peut faire correspondre la chaîne vide ou non, et quels sont les premiers caractères possibles dans une correspondance. Nous voulons les fonctions
empty :: Parser a -> Bool
first :: Parser a -> Set Char
Avec un analyseur applicatif, nous pouvons facilement répondre à cette question. (Je triche un peu ici. Imaginons que nous ayons un constructeur de données correspondant à (<*>)
y (>>=)
dans les "langues" de notre analyseur candidat).
empty (f <*> x) = empty f && empty x
first (f <*> x) | empty f = first f `union` first x
| otherwise = first f
Cependant, avec un analyseur monadique, nous ne savons pas quelle est la grammaire de la deuxième partie sans connaître l'entrée.
empty (x >>= f) = empty x && empty (f ???)
first (x >>= f) | empty x = first x `union` first (f ???)
| otherwise = first x
En autorisant plus, nous sommes en mesure de raisonner moins. Ceci est similaire au choix entre les systèmes de types dynamiques et statiques.
Mais quel est le but de tout cela ? A quoi pourrait servir cette connaissance statique supplémentaire ? Eh bien, nous pouvons par exemple l'utiliser pour éviter le retour en arrière dans l'analyse syntaxique LL(1) en comparant le caractère suivant à l'indice first
de chaque alternative. Nous pouvons également déterminer de manière statique si cela serait ambigu en vérifiant si la fonction first
les ensembles de deux alternatives se chevauchent.
Un autre exemple est qu'il peut être utilisé pour la récupération d'erreurs, comme le montre l'article Analyseurs combinatoires déterministes et correcteurs d'erreurs par S. Doaitse Swierstra et Luc Duponcheel.
En général, cependant, le choix entre l'analyse applicative et monadique a déjà été fait par les auteurs de la bibliothèque d'analyse que vous utilisez. Lorsqu'une bibliothèque telle que Parsec expose les deux interfaces, le choix de celle à utiliser est purement stylistique. Dans certains cas, le code applicatif est plus facile à lire que le code monadique et parfois c'est l'inverse.