2012-01-04 5 views
9

Mam kilka zagnieżdżonych rekordów, które muszę sprawdzić, i zastanawiam się, co jest idiotyczną metodą Haskell, aby to zrobić.Validations in Haskell

Upraszczając:

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

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

Wymagania są następujące:

  • zbierać i zwrócić wszystkie błędy sprawdzania poprawności
  • Niektóre walidacji może być po drugiej pozycji, na przykład ItemsA przeciwko ItemB
  • String s są wystarczające do reprezentowania błędy

Obecnie mam kod, który czuje się niezręcznie:

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] 
+1

czy rozważasz https://bitbucket.org/dibblego/validation? –

+0

Dzięki za sugestię, wygląda bardzo interesująco. Ten sam projekt używa uu-parsinglib do analizowania, więc sprawdzanie stylu aplikacji byłoby dobrym rozwiązaniem. –

+0

Noob pytanie tutaj: co to jest (...) zapis? – Simon

Odpowiedz

4

Co masz już jest w zasadzie w porządku, po prostu potrzebuje trochę Clean-up:

  • Poddane walidacji powinny być definicjami najwyższego poziomu, ponieważ są one dość zaangażowane. (Nawiasem mówiąc, wpisz podpisy na where definicji klauzuli są zazwyczaj pomijane.)
  • Brak konwencji zgodne nazewnictwa
  • Dużo (++) S w sekwencji można dostać brzydki - użyj concat (albo unwords) zamiast
  • Minor formatowanie dziwactwa (pewne zbędne nawiasy concat . map f jest concatMap f itp)

Produkt ten wszystkie:

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 

Myślę, że to całkiem nieźle. Jeśli nie podoba ci się zapis listy można użyć Writer [ErrorMsg] monady:

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] 
+0

Czy to prawda? Zobacz http://stackoverflow.com/questions/8731858/does-writer-monad-guarantee-right-associative-concatenation – pat

+0

@pat: Huh, dobrze, że jesteś. ve usunąłem tabelę z mojej odpowiedzi – ehird

+0

Powinieneś użyć 'Data.Sequence' i zastąpić' [ErrorMsg] '' (Seq ErrorMsg) 'jako' Monitoidem' .Kiedy skończyłeś 'Writer', możesz włączyć "Seq ErrorMsg' do' [ErrorMsg] 'z' Data.Foldable.toList'. – pat

1

budynek na odpowiedź użytkownika @ ehird, można wprowadzić Validate typeclass:

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 
+1

Nie sądzę, że to koniecznie dobry pomysł; proste funkcje utrzymują rzeczy prostsze, a jeśli kiedykolwiek istnieją dwa różne rodzaje sprawdzania poprawności, które można zastosować do jednego typu, spada to. Podnoszenie list jest jednak sprytne. – ehird

+0

To prawda, ale czy nie można było zrobić tego samego argumentu na temat jakiejkolwiek czcionki typograficznej ...? np. co jeśli istnieją dwa różne rodzaje programów, które można zastosować do jednego typu? – pat

+1

Faktycznie, dlatego jestem konserwatywny w używaniu typeclasses :) 'Show' ma ograniczenie, które w zasadzie służy tylko do debugowania i szybkich hacków, jego wyjście powinno być składniowo poprawnym Haskellem, i powinno być ono korzystnie semantycznie poprawnym Haskellem, który ocenia do wartości równej argumentowi przekazanemu do 'show'. Większość życzeń dotyczących "alternatywnych" instancji "show" próbuje przeciwdziałać tym nieformalnym ograniczeniom. Chodzi o kompromisy; na przykład nie ma zbyt wiele chęci używania dwóch zestawów funkcji numerycznych na tym samym typie, a jeśli jest, to jest znacznie przeważony przez wygodę 'Num'. – ehird

3

Czytaj the 8 ways to report errors in Haskell artykuł. W twoim konkretnym przypadku, ponieważ musisz zebrać wszystkie błędy, a nie tylko pierwsze, podejście z monadą Writer zasugerowaną przez @ehird wydaje się pasować najlepiej, ale dobrze jest znać inne wspólne podejścia.

0

Jedną z rzeczy, które możesz chcieć wypróbować, jest zamiast późniejszego sprawdzania danych, użycie soczewek z doskonałego pakietu fclabels jako interfejsu do danych (a nie konstruktorów dopasowujących wzór/typ), aby zapewnić, że twoje dane są zawsze poprawne .

Sprawdź wariant, który obsługuje błąd here i zbuduj soczewkę, przekazując program ustawiający i pobierający, który dokonuje sprawdzenia poprawności na typie danych do funkcji lens.

Jeśli potrzebujesz bardziej skomplikowanego raportu o błędach lub podobnego, spójrz na implementation z Maybe wariant lens i zdefiniuj swój obiektyw pod kątem abstrakcyjnego interfejsu.