Pregunta

Estoy tratando de utilizar MonadError junto con Parsec. Yo he llegado con el siguiente fragmento de código:

f5 = do
    char 'a'
    throwError "SomeError"

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

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

Sin embargo, es ret Left "SomeError", parece que el catchError no tiene ningún efecto. ¿Cuál es la forma correcta de usar MonadError aquí?

Yo prefiero utilizar MonadError sobre la propia gestión de errores de Parsec, como por ejemplo cuando tengo:

try (many1 parser1) <|> parser2

Si parser1 falla aquí, parser2 continuará, pero me gustaría tener una excepción que aborta el análisis completo.

¿Fue útil?

Solución

Estoy bajo la impresión de que usted está tratando de involucrar a MonadError por la razón equivocada.

En el try (many1 parser1) <|> parser2, el comportamiento que usted está tratando de evitar deriva del uso de try y <|> - si no lo hace como él, se utilizan diferentes combinadores. Tal vez una expresión como (many1 parser1) >> parser2 funcionaría mejor para usted? (Esta función descarta los resultados de (many1 parser1);. Que podría, por supuesto uso >>= y combinar los resultados de (many1 parser1) con los de parser2)


(Nota: Por debajo de este punto, no hay una muy buena solución para el problema en cuestión, sólo algunas reflexiones en cuanto a por qué algunas cosas probablemente no trabajo ... Con suerte esto puede ser (un poco) que alumbra, pero no espere demasiado).

A examen más detenido de la interacción ParsecT / MonadError. Me temo que es un poco complicado y todavía no estoy realmente seguro de la mejor manera de ir haciendo lo que el PO quiere hacer, pero estoy esperando la siguiente voluntad, al menos, dar una idea de las razones de la falta de éxito de el enfoque original.

En primer lugar, cabe destacar que no es correcto decir que Parsec es una instancia de MonadError . Parsec es la mónada producido por ParsecT cuando la mónada interior es de identidad; ParsecT produce casos de MonadError si y sólo si se le da una mónada interior que es en sí misma una instancia de MonadError de trabajar. fragmentos relevantes de interacciones 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)

A continuación, Vamos a echar a nosotros mismos un ejemplo de trabajo con catchError y ParsecT . Considere esta interacción GHCi:

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

Aparece el tipo de anotación necesaria (esto parece tener sentido intuitivo para mí, pero no es pertinente a la pregunta original, por lo que no voy a tratar de elaborar). El tipo de toda la expresión se determina por GHC ser como sigue:

Either String (Either ParseError Char)

Por lo tanto, tenemos un resultado de análisis regulares - Either ParseError Char - envuelto en la mónada Either String en lugar de la mónada Identity habitual. Desde Either String es una instancia de MonadError, podemos usar throwError / catchError, pero el controlador pasa al mosto catchError por supuesto producir un valor del tipo correcto. Eso no es muy útil para salir de la rutina de análisis, me temo.

Volver al código de ejemplo de la cuestión. Eso hace algo ligeramente diferente. Vamos a examinar el tipo de ret como se define en la pregunta:

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

(Según GHCi ... nota que tenía que levantar la restricción monomorphism con {-# LANGUAGE NoMonomorphismRestriction #-} tener la compilación de código sin anotaciones de tipos.)

Ese tipo es una indirecta en cuanto a las posibilidades de hacer algo divertido con ret. Aquí vamos:

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

En retrospectiva, el manejador dado a catchError produce un valor utilizando unexpected, así que por supuesto que va a ser (utilizable como) un analizador ... Y me temo que no veo cómo este martillo en algo útil para romper el proceso de análisis.

Otros consejos

Si usted está intentando depurar un analizador para solucionar problemas, es probable que sea más fácil de usar error, Debug.Trace o lo que sea.

Por otro lado, si usted necesita para terminar el análisis de algunos insumos como parte de su programa en sí, pero no es hacerlo debido a una construcción try (...) <|>, entonces usted tiene un bug en su lógica y usted debe parar y reflexionar sobre el sistema gramatical, en lugar de cortar alrededor de ella con el tratamiento de errores.

Si desea que el analizador para terminar en una entrada determinada parte del tiempo, pero no a otros, entonces o bien que falta algo en su flujo de entrada (y debe ser añadido) o un programa de análisis no es la solución a su problema.

Si quiere que el analizador recupera correctamente los errores no fatales y seguir intentando siempre que sea posible, pero termina con un error cuando no puede continuar, entonces ... puede considerar algo más que Parsec, porque es realmente no está diseñado para eso. Creo soportes biblioteca Haskell analizador combinador de la Universidad de Utrecht ese tipo de lógica mucho más fácilmente.

Editar : En lo que Parsec ser mismo una instancia de MonadError va - Sí, y gastos de su propio error subsume esa funcionalidad. Lo que estamos tratando de hacer es una pila segundo mónada de error en la parte superior de la Parsec, y es probable que tiene problemas porque generalmente es difícil de distinguir entre transformadores monad que son "redundante" de esa manera. Tratar con múltiples mónadas Estado es más famoso incómodo, por lo que Parsec (un Estado mónada también) proporciona funcionalidad a otro encargo espera.

En otras palabras, Parsec ser una mónada de error no le ayuda en absoluto, y de hecho es relevante sobre todo en el sentido de hacer el problema más difícil.

si es necesario terminar el análisis de algunos insumos como parte de su programa en sí, pero no es hacerlo debido a una oportunidad (...) <|> construcción, entonces usted tiene un error en la lógica y usted debe parar y reflexionar sobre el sistema gramatical, en lugar de cortar alrededor de ella con el tratamiento de errores.

Si desea que el analizador para terminar en una entrada determinada parte del tiempo, pero no a otros, entonces o bien que falta algo en su flujo de entrada (y debe ser añadido) o un programa de análisis no es la solución a su problema.

Esta respuesta se basa en la suposición de que el problema radica en la gramática. Pero si estoy usando la gramática para alimentar un compilador, hay otros errores que una gramática no puede manejar. Digamos que una referencia variable, a una variable que no estaba definido. Y el lenguaje se especifica como una sola pasada, y las variables se evalúan como se encuentra. Entonces, la gramática está bien. El análisis está bien. Sin embargo, como resultado de la evaluación de lo que se especifica en la gramática ha producido un error, el vigente "a prueba" o "inesperado" o insuficientes para hacer frente a este problema. Sería bueno tener un medio para abortar el análisis sin tener que recurrir a la gestión de errores de nivel superior.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top