Как сопоставить шаблон с абстрактным типом данных, когда конструктор данных не находится в области видимости? - PullRequest
1 голос
/ 05 мая 2019

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

dash :: GenParser Char st Char
dash = char '-'

Я бы хотел написать несколько тестов для этого. Положительный тест довольно прост:

spec :: Spec
spec = do
  describe "dash" $ do
    it "parses a dash" $
      parse dash "N/A" "-" `shouldBe` (Right '-')

Я бы тоже хотел написать отрицательный тест. Когда парсер не совпадает, он возвращает Left из ParseError. Я хотел бы написать тест, который проверяет точное сообщение, которое содержит ParseError. Так что я действительно хотел бы сделать что-то вроде

spec :: Spec
spec = do
  describe "dash" $ do
    it "doesn't parse an underscore" $
      parse dash "N/A" "_" `shouldSatisfy` (hasErrorMessage "not a dash")

hasErrorMessage (Left (ParseError _ msgs)) expected = msg == expected
hasErrorMessage _ expected = False

Но у меня возникают проблемы при написании такого рода кода, поскольку конструктор данных ParseError не экспортируется из Text.Parsec.Error.

Есть ли способ использовать сопоставление с образцом в типах, где в области действия нет конструктора данных для этого типа?

Я знаю, что мог бы написать hasErrorMessage что-то вроде

hasErrorMessage :: String -> (Either ParseError a) -> Bool
hasErrorMessage expected (Left pe) = elem expected $ fmap messageString (errorMessages pe)

но я бы тоже хотел понять этот нюанс.

1 Ответ

3 голосов
/ 05 мая 2019

Хотя конструктор данных не экспортируется, функции для доступа к его параметрам есть.Вы можете использовать их в сочетании с шаблонами просмотра , чтобы получить то, что вы хотите.В вашем случае шаблон (errorMessages -> msgs) может почти идеально подходить для (ParseError _ msgs) с двумя оговорками:

  1. Вам необходимо {-# LANGUAGE ViewPatterns #-}, чтобы использовать эту функцию.
  2. errorMessages сортирует сообщения, что не подходит для сопоставления с шаблоном в конструкторе данных.

Вы можете даже использовать эту технику с синонимами шаблона , чтобы создать поддельный конструктор данных, поэтомуВы можете использовать тот же синтаксис, что и в противном случае:

{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
pattern ParseError pos msgs <- ((,) <$> errorPos <*> errorMessages -> (pos, msgs)) where
        ParseError pos msgs = foldr addErrorMessage (newErrorUnknown pos) msgs
...