Pergunta

Eu estou tentando usar o MonadError juntamente com o Parsec.Eu vim com o seguinte trecho de código:

f5 = do
    char 'a'
    throwError "SomeError"

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

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

No entanto, ret é Left "SomeError", parece que o catchError não tem qualquer efeito.Qual é a maneira certa de usar MonadError aqui?

Eu prefiro usar MonadError mais de Parsec é próprio de tratamento de erro, como, por exemplo, quando eu tenho:

try (many1 parser1) <|> parser2

Se parser1 falha aqui, parser2 vai continuar, mas eu gostaria de ter uma exceção que anula a análise inteiramente.

Foi útil?

Solução

Estou com a impressão de que você está tentando envolver MonadError pela razão errada.

No try (many1 parser1) <|> parser2, o comportamento que você está tentando evitar hastes do uso de try e <|> - Se você não gosta, use combinadores diferentes. Talvez uma expressão como (many1 parser1) >> parser2 funcionaria melhor para você? (Isso descarta os resultados de (many1 parser1); você poderia usar obviamente >>= e combinar os resultados de (many1 parser1) com aqueles de parser2.)


(Nota: abaixo deste ponto, não há uma solução realmente boa para o problema em questão, apenas algumas reflexões sobre por que algumas coisas provavelmente não funcionam ... espero que isso possa ser (um pouco) esclarecedor, mas não espere também Muito de.)

Um exame mais detalhado da interação parsect / monaderror. Receio que seja um pouco bagunçado e ainda não tenho muita certeza da melhor forma de fazer o que o OP quer fazer, mas espero que o seguinte forneça pelo menos uma visão das razões para a falta de sucesso de a abordagem original.

Em primeiro lugar, observe que Não é correto dizer que o parsec é uma instância de Monaderror. Parsec é a mônada produzida por parsect quando a mônada interna é a identidade; O Parsect produz casos de Monaderror se e somente se recebe uma mônada interna, que é uma instância de Monaderror para trabalhar. Fragmentos relevantes das interações 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)

Próximo, Vamos ter um exemplo de funcionamento com Catcherror e Parsect. Considere esta interação GHCI:

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

A anotação do tipo parece necessária (isso parece fazer sentido intuitivo para mim, mas não é pertinente à pergunta original, por isso não vou tentar elaborar). O tipo de toda a expressão é determinado pelo GHC como a seguinte:

Either String (Either ParseError Char)

Então, temos um resultado regular de análise - Either ParseError Char - envolto no Either String Mônada no lugar do habitual Identity Mônada. Desde Either String é uma instância de MonadError, podemos usar throwError / catchError, mas o manipulador passou para catchError É claro que deve produzir um valor do tipo correto. Isso não é muito útil para sair da rotina de análise, receio.

Voltar ao código de exemplo da pergunta. Isso faz uma coisa um pouco diferente. Vamos examinar o tipo de ret Conforme definido na pergunta:

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

(De acordo com GHCI ... observe que eu tive que levantar a restrição de monomorfismo com {-# LANGUAGE NoMonomorphismRestriction #-} Para ter o código compilar sem anotações de tipo.)

Esse tipo é uma dica quanto à possibilidade de fazer algo divertido com ret. Aqui vamos nós:

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

Em retrospectiva, o manipulador dado a catchError produz um valor usando unexpected, então é claro que será (utilizável) um analisador ... e tenho medo de não ver como martelar isso em algo útil para sair do processo de análise.

Outras dicas

Se você está tentando depurar um analisador para solucionar problemas, provavelmente é mais simples de usar error, Debug.Trace, ou outros enfeites.

Por outro try (...) <|> construir, então você tem um Bug em sua lógica E você deve parar e repensar sua gramática, em vez de invadi -la com o manuseio de erros.

Se você deseja que o analisador termine em uma determinada entrada algumas vezes, mas não outras, algo está faltando no seu fluxo de entrada (e deve ser adicionado) ou um analisador não é a solução para o seu problema.

Se você deseja que o analisador se recupere graciosamente de erros não fatais e continue tentando quando possível, mas terminando com um erro quando não pode continuar, então você ... pode querer considerar algo diferente de parsec, porque realmente não foi projetado por isso. Acredito que a Biblioteca Haskell Parser Combinator da Utrecht University suporta esse tipo de lógica com muito mais facilidade.

Editar: Quanto ao parsec ser um exemplo de MonadError Basta-sim, e seu próprio tratamento de erros inclui essa funcionalidade. O que você está tentando fazer é empilhar um segundo Monad de erros no topo do parsec, e você provavelmente está tendo problemas, porque geralmente é estranho distinguir entre transformadores da Monad que são "redundantes" dessa maneira. Lidar com várias mônadas estaduais é mais famoso, e é por isso que o Parsec (uma Mônada do Estado também) fornece funcionalidade para manter o estado personalizado.

Em outras palavras, o parsec é uma mônada de erro não ajuda você e, de fato, é relevante principalmente no sentido de dificultar o seu problema.

se você precisa terminar a análise sobre alguns insumos, como parte de seu programa real, mas não fazê-lo devido a uma tentativa (...) <|> construir, então você tem um erro em sua lógica e você deve parar e repensar sua gramática, em vez de hack com o erro de manipulação.

Se você deseja que o analisador de terminar em uma dada entrada algumas vezes, mas outras não, então algo está em falta a partir do seu fluxo de entrada (e deve ser adicionado) ou um analisador não é a solução para o seu problema.

Esta resposta baseia-se no pressuposto de que o problema está na gramática.Mas se eu estou usando a gramática para alimentar um compilador, existem outros erros que uma gramática não pode manipular.Vamos dizer que uma variável de referência, para uma variável que não foi definida.E a linguagem é especificado como uma única passagem, e as variáveis são avaliadas como encontrou.Em seguida, a gramática é apenas multa.A análise é apenas multa.Mas como um resultado de avaliar o que foi especificado na gramática ocorreu um erro, a existência de "falhas" ou "inesperado" ou insuficientes para lidar com este problema.Seria bom ter um meio de anular a análise, sem recorrer a um maior nível de tratamento de erro.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top