Использование MonadError с Parsec - PullRequest
3 голосов
/ 02 февраля 2010

Я пытаюсь использовать 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будет продолжаться, но я хотел бы иметь исключение, которое полностью прерывает синтаксический анализ.

Ответы [ 3 ]

4 голосов
/ 02 февраля 2010

У меня сложилось впечатление, что вы пытаетесь привлечь MonadError по неправильной причине.

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


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

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

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

Далее, давайте разберемся с рабочим примером с catchError и ParsecT . Рассмотрим это взаимодействие 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, поэтому, конечно, это будет (можно использовать как) синтаксический анализатор ... И я боюсь, что не вижу, как забить это полезно для выхода из процесса синтаксического анализа.

2 голосов
/ 23 апреля 2010

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

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

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

2 голосов
/ 02 февраля 2010

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

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

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

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

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

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

...