Реализация "строки" в Text.Parser.Char

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

Попробуйте написать парсер, который делает что string делает, но используя char,

Я не мог понять это, поэтому я проверил источник для реализации. В настоящее время я пытаюсь обернуть голову вокруг этого. Вот:

class Parsing m => CharParsing m where
    -- etc.
    string :: CharParsing m => String -> m String
    string s = s <$ try (traverse_ char s) <?> show s

Мои вопросы следующие, от самых до наименее конкретных.

  1. Почему show необходимо?

  2. Почему s <$ необходимо? не traverse char s <?> s работать так же? Другими словами, почему мы отбрасываем результаты обхода?

  3. Что происходит с обходом? Я получаю то, что делает обход списка, так что, думаю, я запутался в экземплярах Applicative/Monad для Parser. На высоком уровне я понимаю, что обход применяется char, который имеет тип CharParsing m => Char -> m Charдля каждого символа в строке s, а затем собирает все результаты во что-то типа Parser [Char], Так что типы имеют смысл, но я понятия не имею, что происходит в фоновом режиме.

Заранее спасибо!

1 ответ

1) Почему show необходимо?

Так как showв строке (или Textи т. д.) экранирует специальные символы, что имеет смысл для сообщений об ошибках:

GHCi> import Text.Parsec -- Simulating your scenario with Parsec.
GHCi> runParser ((\s -> s <$ try (traverse_ char s) <?> s) "foo\nbar") () "" "foo"
Left (line 1, column 4):
unexpected end of input
expecting foo
bar
GHCi> runParser ((\s -> s <$ try (traverse_ char s) <?> show s) "foo\nbar") () "" "foo"
Left (line 1, column 4):
unexpected end of input
expecting "foo\nbar"

2) Почему s <$ необходимо? Не пересекает char s <?> s работать так же? Другими словами, почему мы отбрасываем результаты обхода?

Результат разбора не нужен, потому что мы заранее знаем, что это будет s (если разбор был успешным). traverse будет без необходимости реконструировать s из результатов разбора каждого отдельного символа. В общем, если результаты не нужны, рекомендуется использовать traverse_ (который просто объединяет эффекты, отбрасывая результаты, не пытаясь перестроить структуру данных), а не traverse, так что, вероятно, функция написана так, как она есть.

3) Что происходит с обходом?

traverse_ char s (traverse_, и не traverse, как объяснено выше) это парсер. Он пытается разобрать по порядку каждый символ в s, отбрасывая результаты, и он строится путем секвенирования парсеров для каждого символа в s, Может быть полезно напомнить, что traverse_ это просто складка, которая использует(*>):

-- Slightly paraphrasing the definition in Data.Foldable:
traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()
traverse_ f = foldr (\x u -> f x *> u) (pure ())
Другие вопросы по тегам