Вопрос

Я пытаюсь использовать MonadError вместе с Parsec.Я придумал следующий фрагмент кода:

f5 = do
    char 'a'
    throwError "SomeError"

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

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

Однако, ret является Left "SomeError", кажется, что catchError не имеет никакого эффекта.Как правильно использовать MonadError здесь?

Я бы предпочел использовать MonadError вместо собственной обработки ошибок Parsec, как, например, когда у меня есть:

try (many1 parser1) <|> parser2

Если здесь произойдет сбой parser1, parser2 продолжит работу, но мне бы хотелось иметь исключение, которое полностью прерывает анализ.

Это было полезно?

Решение

Я под впечатлением, что вы пытаетесь привлечь MonadError по неправильной причине.

в try (many1 parser1) <|> parser2, поведение, которое вы пытаетесь избежать связанных с использованием try а также <|> - Если вам это не нравится, используйте разные комбинаторы. Возможно выражение, как (many1 parser1) >> parser2 будет работать лучше для вас? (Это отбрасывает результаты из (many1 parser1); Вы, конечно, могли использовать >>= и объединить результаты из (many1 parser1) с теми из parser2.)


(Примечание. Ниже этого момента не очень хорошего решения проблемы под рукой, просто некоторые размышления о том, почему некоторые вещи, вероятно, не будут работать ... Надеюсь, это может быть (несколько) просвещать, но не ожидай много.)

Более тесное исследование обмена / монадерорного взаимодействия. Боюсь, это немного грязно, и я до сих пор не уверен, как лучше пойти делать то, что OP хочет сделать, но я надеюсь, что следующее будет по крайней мере обеспечить понимание причин отсутствия успеха Оригинальный подход.

Во-первых, обратите внимание, что Неверно сказать, что Parsec является экземпляром монадерора. Отказ Parsec является монадом, производимым путем обмена, когда внутренний монад является личностью; Parsct производит экземпляры монадеррара, если и только в том случае, если он дан внутренний монаде, который сам по себе является примером монадерора для работы. Соответствующие фрагменты взаимодействия 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)

Следующий, Давайте имеем себе рабочий пример с CavaError и Parsct. Отказ Рассмотрим это взаимодействие GHCI:

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

Аннотация типа возникает необходимым (это, похоже, это делает для меня интуитивно понятным смыслом, но это не уместно к первоначальному вопросу, поэтому я не буду пытаться уточнить). Тип всего выражения определяется GHC следующим образом:

Either String (Either ParseError Char)

Итак, у нас есть обычный результат анализа - Either ParseError Char - завернутый в Either String Монад вместо обычного Identity монад С Either String является примером MonadError, мы можем использовать throwError / catchError, но обработчик передал catchError Конечно, необходимо создать значение правильного типа. Это не очень полезно для вырыва рутины разборки, я боюсь.

Вернуться к примеру кода с вопроса. Это делает немного по-другому. Давайте рассмотрим тип ret Как определено в вопросе:

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

(По данным GHCI ... Обратите внимание, что мне пришлось поднять ограничение мономорфизма с {-# LANGUAGE NoMonomorphismRestriction #-} иметь компиляцию кода без аннотаций типа.)

Этот тип - подсказка относительно возможности что-то забавное ret. Отказ Вот так:

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

Оглядываясь, обработчик, данный catchError производит значение, использующее unexpected, Так, конечно, это будет (пригодным для использования как) парсер ... И я боюсь, что я не вижу, как забить это в нечто полезное для разрыва процесса анализа.

Другие советы

Если вы пытаетесь отладить парсер для устранения неполадок, возможно, его проще использовать. error, Debug.Trace, или еще что.

С другой стороны, если вам нужно прекратить синтаксический анализ некоторых входных данных в рамках вашей реальной программы, но это не происходит из-за try (...) <|> построить, то у вас есть ошибка в твоей логике и вам следует остановиться и переосмыслить свою грамматику, а не возиться с обработкой ошибок.

Если вы хотите, чтобы синтаксический анализатор завершал работу на определенном входе в некоторых случаях, но не в других, то либо в вашем входном потоке чего-то не хватает (и его следует добавить), либо синтаксический анализатор не является решением вашей проблемы.

Если вы хотите, чтобы синтаксический анализатор корректно восстанавливался после нефатальных ошибок и продолжал попытки, когда это возможно, но завершался с ошибкой, когда он не мог продолжать работу, тогда вы...возможно, стоит подумать о чем-то другом, кроме Parsec, потому что он действительно не предназначен для этого.Я считаю, что библиотека комбинаторов парсеров Haskell Утрехтского университета гораздо проще поддерживает такую ​​логику.

Редактировать:Поскольку Парсек сам является примером MonadError идет - да, и его собственная обработка ошибок включает в себя эту функциональность.То, что вы пытаетесь сделать, это сложить второй error monad поверх Parsec, и у вас, вероятно, возникли проблемы, потому что обычно неудобно различать монадные преобразователи, которые являются «избыточными» таким образом.Работа с несколькими монадами состояний более известна как неуклюжая задача, поэтому Parsec (также монада состояний) предоставляет функциональность для хранения пользовательского состояния.

Другими словами, Parsec, являющийся монадой ошибок, вам совершенно не помогает, а на самом деле имеет значение главным образом в смысле усложнения вашей проблемы.

Если вам нужно завершить анализ на некоторых входах как часть вашей фактической программы, но она не делает это из-за попытки (...) <|> Construct, тогда у вас есть ошибка в вашей логике, и вы должны остановиться и переосмыслить Ваша грамматика, а не взлом вокруг него с обработкой ошибок.

Если вы хотите, что парсер завершится на данный ввод некоторое время, но не другие, то либо что-то отсутствует от вашего входного потока (и должен быть добавлен), либо парсер не является решением вашей проблемы.

Этот ответ основан на предположении, что проблема лежит в грамматике. Но если я использую грамматику, чтобы накормить компилятор, есть другие ошибки, что грамматика не может обрабатывать. Скажем, переменная ссылка на переменную, которая не была определена. И язык указан как один проход, и переменные оцениваются как встречаются. Затем грамматика просто в порядке. Разборка просто в порядке. Но в результате оценки того, что было указано в грамматике, произошла ошибка, существующая «неудачная» или «неожиданная» или недостаточная для решения этой проблемы. Было бы неплохо иметь средства, чтобы прервать разбор, не прибегая к обработке ошибок более высокого уровня.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top