Проверки в Haskell - PullRequest
       45

Проверки в Haskell

10 голосов
/ 04 января 2012

У меня есть несколько вложенных записей, которые мне нужно проверить, и мне интересно, каков идиоматический способ Haskell сделать это.

Для упрощения:

data Record = Record {
  recordItemsA :: [ItemA],
  recordItemB :: ItemB
} deriving (Show)

data ItemA {
  itemAItemsC :: [ItemC]
} deriving (Show)

Требования:

  • Собрать и вернуть все ошибки проверки
  • Некоторые проверки могут выполняться по элементам, например, ItemsA против ItemB
  • String достаточно для представления ошибок

В настоящее время у меня есть код, который кажется неудобным:

type ErrorMsg = String

validate :: Record -> [ErrorMsg]
validate record =
  recordValidations ++ itemAValidations ++ itemBValidations
  where
    recordValidations :: [ErrorMsg]
    recordValidations = ensure (...) $
      "Invalid combination: " ++ (show $ recordItemsA record) ++ " and " ++ (show $ recordItemsB record)
    itemAValidations :: [ErrorMsg]
    itemAValidations = concat $ map validateItemA $ recordItemsA record
    validateItemA :: ItemA -> [ErrorMsg]
    validateItemA itemA = ensure (...) $
      "Invalid itemA: " ++ (show itemA)
    itemBValidations :: [ErrorMsg]
    itemBValidations = validateItemB $ recordItemB record
    validateItemB :: ItemB -> [ErroMsg]
    validateItemB itemB = ensure (...) $
      "Invalid itemB: " ++ (show itemB)

ensure :: Bool -> ErrorMsg -> [ErrorMsg]
ensure b msg = if b then [] else [msg]

Ответы [ 4 ]

4 голосов
/ 04 января 2012

Прочитайте 8 способов сообщить об ошибках в статье на Haskell .Для вашего конкретного случая, так как вам нужно собрать все ошибки, а не только первую, подход с монадой Writer, предложенный @ehird, кажется, подходит лучше всего, но полезно знать другие распространенные подходы.

4 голосов
/ 04 января 2012

То, что у вас уже есть, в основном хорошо, просто нужно немного почистить:

  • Суб-валидации должны быть определениями верхнего уровня, так как они довольно сложны. (Кстати, подписи типов в определениях предложений where обычно опускаются.)
  • Отсутствие согласованного соглашения об именах
  • Множество последовательностей (++) может стать ужасным - используйте concat (или, возможно, unwords) вместо
  • Незначительные причуды форматирования (есть некоторые лишние скобки, concat . map f - это concatMap f и т. Д.)

Произведение всего этого:

validateRecord :: Record -> [ErrorMsg]
validateRecord record = concat
  [ ensure (...) . concat $
      [ "Invalid combination: ", show (recordItemsA record)
      , " and ", show (recordItemB record)
      ]
  , concatMap validateItemA $ recordItemsA record
  , validateItemB $ recordItemB record
  ]

validateItemA :: ItemA -> [ErrorMsg]
validateItemA itemA = ensure (...) $ "Invalid itemA: " ++ show itemA

validateItemB :: ItemB -> [ErrorMsg]
validateItemB itemB = ensure (...) $ "Invalid itemB: " ++ show itemB

Я думаю, это довольно хорошо. Если вам не нравится нотация списка, вы можете использовать монаду Writer [ErrorMsg]:

validateRecord :: Record -> Writer [ErrorMsg] ()
validateRecord record = do
  ensure (...) . concat $
    [ "Invalid combination: ", show (recordItemsA record)
    , " and ", show (recordItemB record)
    ]
  mapM_ validateItemA $ recordItemsA record
  validateItemB $ recordItemB record

validateItemA :: ItemA -> Writer [ErrorMsg] ()
validateItemA itemA = ensure (...) $ "Invalid itemA: " ++ show itemA

validateItemB :: ItemB -> Writer [ErrorMsg] ()
validateItemB itemB = ensure (...) $ "Invalid itemB: " ++ show itemB

ensure :: Bool -> ErrorMsg -> Writer [ErrorMsg] ()
ensure b msg = unless b $ tell [msg]
1 голос
/ 04 января 2012

Опираясь на ответ @ehird, вы можете ввести Validate класс типов:

class Validate a where
  validate :: a -> [ErrorMsg]

instance Validate a => Validate [a] where
  validate = concatMap validate

instance Validate Record where
  validate record = concat
    [ ensure (...) . concat $
      [ "Invalid combination: ", show (recordItemsA record)
      , " and ", show (recordItemB record)
      ]
    , validate $ recordItemsA record
    , validate $ recordItemB record
    ]

instance Validate ItemA where
  validate itemA = ensure (...) $ "Invalid itemA: " ++ show itemA

instance Validate ItemB where
  validate itemB = ensure (...) $ "Invalid itemB: " ++ show itemB
0 голосов
/ 05 января 2012

Одна вещь, которую вы могли бы попробовать - вместо проверки ваших данных потом использовать линзы из превосходного пакета fclabels в качестве интерфейса к вашим данным (а не конструкторы сопоставления с образцом / типа), чтобы Ваши данные всегда верны.

Ознакомьтесь с вариантом, поддерживающим сбой здесь , и постройте свой объектив, передав сеттер и геттер, выполняющие некоторую проверку типа данных, в функцию lens.

Если вам нужны более сложные отчеты об ошибках или еще что-то, посмотрите на реализацию варианта Maybe lens и определите свой объектив в терминах абстрактного интерфейса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...