Question

Je suis en train d'utiliser MonadError avec parsecs. Je suis venu avec l'extrait de code suivant:

f5 = do
    char 'a'
    throwError "SomeError"

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

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

Cependant, ret est Left "SomeError", il semble que le catchError n'a pas d'effet. Quelle est la bonne façon d'utiliser MonadError ici?

Je préférerais utiliser MonadError sur propre gestion des erreurs de parsecs, comme par exemple quand j'ai:

try (many1 parser1) <|> parser2

Si parser1 échoue ici, parser2 continuera, mais je voudrais avoir une exception qui avorte l'analyse entièrement.

Était-ce utile?

La solution

Je suis sous l'impression que vous essayez d'impliquer MonadError pour la mauvaise raison.

Dans le try (many1 parser1) <|> parser2, le comportement que vous essayez d'éviter découle de l'utilisation de try et <|> - si vous ne l'aimez pas, utilisez différents combinateurs. Peut-être une expression comme (many1 parser1) >> parser2 fonctionnerait mieux pour vous? (Ce défausse les résultats de (many1 parser1);. Vous pourriez l'utilisation des cours >>= et combiner les résultats de (many1 parser1) avec ceux de parser2)


(Note: En dessous de ce point, il n'y a pas vraiment une bonne solution au problème à portée de main, juste quelques réflexions pour expliquer pourquoi certaines choses ne fonctionneront probablement pas ... Espérons que cela peut être (un peu) éclairant, mais ne vous attendez pas trop.)

Un examen plus approfondi de l'interaction ParsecT / MonadError. Je crains que c'est un peu en désordre et je ne suis toujours pas vraiment sûr de la meilleure façon d'aller à faire ce que l'OP veut faire, mais j'espère que ce qui suit va au moins donner un aperçu des raisons du manque de succès de l'approche originale.

Tout d'abord, notez que il est inexact de dire que parsec est une instance de MonadError . Parsec est la monade produit par ParsecT lorsque la monade intérieure est d'identité; ParsecT produit des cas de MonadError si et seulement si elle reçoit une monade intérieure qui lui-même est une instance de MonadError à travailler avec. fragments pertinents des interactions 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)

Ensuite, nous allons avoir nous un exemple de travail avec catchError et ParsecT . Considérez cette interaction GHCi:

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

L'annotation de type apparaît nécessaire (ce qui semble logique intuitive pour moi, mais ce n'est pas pertinente à la question initiale, je ne vais pas essayer d'élaborer). Le type de l'expression entière est déterminée par GHC être la suivante:

Either String (Either ParseError Char)

Alors, nous avons un résultat d'analyse syntaxique régulière - Either ParseError Char - enveloppé dans la monade Either String à la place de la monade habituelle Identity. Depuis Either String est une instance de MonadError, nous pouvons utiliser throwError / catchError, mais le gestionnaire est passé à catchError doit évidemment produire une valeur du type correct. Ce n'est pas très utile pour sortir de la routine d'analyse syntaxique, j'ai peur.

Retour à l'exemple de code de la question. fait une chose un peu différente. Examinons le type de ret tel que défini dans la question:

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

(Selon GHCi ... A noter que je devais lever la restriction de monomorphisme avec {-# LANGUAGE NoMonomorphismRestriction #-} d'avoir le code compilation sans annotations de type.)

Ce type est une indication quant à la possibilité de faire quelque chose d'amusant avec ret. nous allons ici:

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

Avec le recul, le gestionnaire donné à catchError produit une valeur à l'aide unexpected, donc bien sûr que ça va être (utilisable comme) un analyseur ... Et je crains que je ne vois pas comment marteler cela en quelque chose d'utile pour sortir du processus d'analyse syntaxique.

Autres conseils

Si vous essayez de déboguer un analyseur pour résoudre, il est probablement plus simple à utiliser error, Debug.Trace, ou tout le reste.

Par contre, si vous avez besoin de mettre fin à l'analyse syntaxique sur certaines entrées dans le cadre de votre programme actuel, mais pas le faire en raison d'une construction try (...) <|>, alors vous avez un bug dans votre logique et vous devez arrêter et repenser votre grammaire, plutôt que de pirater autour d'elle avec le traitement des erreurs.

Si vous voulez que l'analyseur de mettre fin à une entrée donnée de temps en temps, mais pas d'autres, alors soit quelque chose qui manque dans votre flux d'entrée (et doit être ajouté) ou un analyseur n'est pas la solution à votre problème.

Si vous voulez que l'analyseur de récupérer grâce à des erreurs non fatales et continuer à essayer lorsque cela est possible, mais se terminent par une erreur quand il ne peut pas continuer, alors vous ... pouvez envisager autre chose que parsec, parce qu'il est vraiment pas conçu pour cela. Je crois que l'analyseur de l'Université d'Utrecht Haskell bibliothèque combinateur soutient ce genre de logique beaucoup plus facilement.

Modifier : En ce qui concerne parsec lui-même étant une instance de MonadError va - oui, et sa propre gestion des erreurs subsume cette fonctionnalité. Qu'est-ce que vous essayez de faire est pile secondes monade d'erreur sur le dessus de parsecs, et que vous rencontrez probablement des problèmes parce qu'il est généralement difficile à distinguer entre les transformateurs de monades qui sont « redondants » de cette manière. Faire face à plusieurs monades État est plus célèbre maladroit, ce qui explique pourquoi parsec (un État monade ainsi) fournit des fonctionnalités pour maintenir l'état personnalisé.

En d'autres termes, parsec étant une monade d'erreur ne vous aide pas du tout, et en fait est surtout pertinent dans le sens de rendre votre problème plus difficile.

si vous avez besoin de mettre fin à l'analyse syntaxique sur certaines entrées dans le cadre de votre programme actuel, mais il fait pas à cause d'un essai (...) <|> construction, alors vous avez un bug dans votre logique et vous devez arrêter et repenser votre grammaire, plutôt que de pirater autour d'elle avec le traitement des erreurs.

Si vous voulez que l'analyseur de mettre fin à une entrée donnée de temps en temps, mais pas d'autres, alors soit il manque quelque chose à partir de votre flux d'entrée (et doit être ajoutée) ou un analyseur n'est pas la solution à votre problème.

Cette réponse est basée sur l'hypothèse que le problème réside dans la grammaire. Mais si j'utilise la grammaire pour alimenter un compilateur, il y a d'autres erreurs qu'une grammaire ne peut pas gérer. Disons une référence variable, à une variable qui n'a pas été définie. Et la langue est spécifiée comme une seule passe, et les variables sont évaluées comme rencontrées. Ensuite, la grammaire est très bien. L'analyse syntaxique est très bien. Mais à la suite de l'évaluation de ce qui a été spécifié dans la grammaire une erreur est survenue, l'existant « échec » ou « inattendu » ou insuffisante pour faire face à ce problème. Ce serait bien d'avoir un moyen d'abandonner l'analyse sans avoir recours à la gestion des erreurs de niveau supérieur.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top