Есть разница, и это важно, но отчасти причина в том, что остальная часть синтаксического анализатора вполне fr agile.
Когда я изменяю decimalParser <|> integerParser
на integerParser <|> decimalParser
все еще кажется, что он всегда анализирует правильные вещи (в частности, я сделал это и запустил тестирование стека, и все их тесты все еще прошли).
Тесты пройдены, потому что тесты не покрывают эта часть синтаксического анализатора (только самые близкие осуществляют stringParser
).
Вот тест, который в настоящее время проходит, но не будет, если вы поменяете местами эти парсеры (вставьте его в test/Spec.hs
и добавьте его в do
блок под main
):
badex :: Spec
badex = describe "Bad example" $ do
it "Should fail" $
shouldMatch
exampleLineParser
"| 3.4 |\n"
[ ValueNumber 3.4 ]
Если вы поменяете местами парсеры, вы получите в результате ValueNumber 3.0
: integerParser
(который теперь первый) успешно разбирает 3
, но затем остальные входные данные отбрасываются.
Чтобы получить больше контекста, мы должны увидеть, где используется numberParser
:
numberParser
является одной из альтернатив valueParser
... - , который используется в
exampleLineParser
, где за valueParser
следует readThroughBar
(и я имею в виду соответствующие кусок кода буквально valueParser <* readThroughBar
); readThroughBar
отбрасывает все символов до следующей вертикальной черты (используя many (psym (\c -> c /= '|' && c /= '\n'))
).
Так если valueParser
удастся выполнить синтаксический анализ всего 3
, то последующее readThroughBar
с удовольствием поглотит и откажется от остальных .4 |
.
Объяснение из цитируемого вами поста блога является лишь частично правильным:
Обратите внимание, что порядок имеет значение! Если мы сначала поставим целочисленный парсер, у нас будут проблемы! Если мы встретим десятичную дробь, целочисленный синтаксический анализатор будет жадно успешен и проанализирует все до десятичной точки. Мы либо потеряем всю информацию после десятичного числа или, что еще хуже, произойдет сбой синтаксического анализа.
(выделение мое) Вы потеряете информацию только в том случае, если ваш анализатор активно ее отбрасывает , что readThroughBar
здесь делает.
Как вы уже предположили, поведение обратного отслеживания RE
означает, что некоммутативность <|>
действительно имеет значение только для корректности с неоднозначными синтаксисами (она все еще может влиять на производительность в целом), что не было бы проблемой, если бы readThroughBar
были менее снисходительными, например, потребляя только пробел до |
.
Я думаю, это показывает, что использование psym
с (/=)
это как минимум кодовый запах, если не чистый антипаттерн. Поиск только ограничителя без ограничения символов в середине затрудняет выявление ошибок, когда предыдущий анализатор не потребляет столько входных данных, сколько должен. Лучшей альтернативой является обеспечение того, чтобы используемые символы не содержали значимой информации, например, требуя, чтобы все они были пробелами.