Реализация "строки" в 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
Мои вопросы следующие, от самых до наименее конкретных.
Почему
show
необходимо?Почему
s <$
необходимо? неtraverse char s <?> s
работать так же? Другими словами, почему мы отбрасываем результаты обхода?Что происходит с обходом? Я получаю то, что делает обход списка, так что, думаю, я запутался в экземплярах 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 ())