2012-01-04 12 views
9

Ho alcuni record nidificati che devo convalidare, e mi chiedo quale sia un modo idiomatico di Haskell per farlo.Convalida in Haskell

Per semplificare:

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

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

I requisiti sono:

  • raccogliere e restituire tutti gli errori di convalida
  • Alcune convalide possono essere tra gli elementi, ad esempio ItemsA contro ItemB
  • String s sono sufficienti a rappresentare errori

Al momento ho il codice che si sente a disagio:

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

hai considerato https://bitbucket.org/dibblego/validation? –

+0

Grazie per un suggerimento, sembra molto interessante. Lo stesso progetto usa uu-parsinglib per l'analisi, quindi la convalida dello stile applicativo sarebbe una buona idea. –

+0

domanda noob qui: qual è la (...) notazione? – Simon

risposta

4

Quello che hai già è fondamentalmente bene, ha solo bisogno di un po 'di clean-up:

  • Le convalide secondarie devono essere definizioni di livello superiore, poiché sono abbastanza coinvolte. (A proposito, digitare firme su where definizioni clausola di solito sono omessi.)
  • Mancanza di convenzione coerente di denominazione
  • sacco di (++) s in sequenza può ottenere brutto - utilizzare concat (o forse unwords) invece
  • formattazione Minore peculiarità (ci sono alcuni parentesi superflui, concat . map f è concatMap f, ecc)

Il prodotto di tutto questo:

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 

Penso che sia abbastanza buono. Se non ti piace la notazione lista, è possibile utilizzare il Writer [ErrorMsg] monade:

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

È vero? Vedi http://stackoverflow.com/questions/8731858/does-writer-monad-guarantee-right-associative-concatenation – pat

+0

@pat: Huh, giusto sei. Ho rimosso la risonanza dalla mia risposta: – ehird

+0

Dovresti usare 'Data.Sequence' e sostituire' [ErrorMsg] 'con' (Seq ErrorMsg) 'come' Monoid'. Quindi, quando il 'Writer' è finito, puoi girare il 'Seq ErrorMsg' in un' [ErrorMsg] 'con' Data.Foldable.toList'. – pat

1

Sulla base di @ ehird risposta, si potrebbe introdurre un 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

Non penso che sia necessariamente una buona idea; le semplici funzioni mantengono le cose più semplici, e se ci sono due diversi tipi di convalida che possono essere applicati a un singolo tipo, questo cade. Tuttavia, il sollevamento delle liste è intelligente. – ehird

+0

Vero, ma non si potrebbe fare lo stesso argomento su qualsiasi classe di tipizzazione ...? e se ci fossero mai due diversi tipi di spettacolo che possono essere applicati a un singolo tipo? – pat

+1

Infatti, è per questo che sono prudente nell'usare i typeclass :) 'Show' ha il limite che è fondamentalmente solo per il debugging e gli hack veloci, il suo output dovrebbe essere Haskell validato in modo sintattico, e preferibilmente dovrebbe essere Haskell semanticamente valido che valuta a un valore uguale all'argomento passato a 'show'. La maggior parte dei desideri di "istanze' Show' alternate "stanno cercando di andare contro questi limiti informali. Si tratta di trade-off; per esempio. non c'è molto desiderio di usare due serie di funzioni numeriche sullo stesso tipo e, se esiste, è pesantemente superata dalla comodità di 'Num'. – ehird

3

Leggi the 8 ways to report errors in Haskell articolo. Per il tuo caso particolare, dato che devi raccogliere tutti gli errori e non solo il primo, l'approccio con Writer monad suggerito da @ehird sembra adattarsi meglio, ma è bene conoscere altri approcci comuni.

0

Una cosa che potresti provare è, piuttosto che convalidare i tuoi dati, utilizzare gli obiettivi dell'eccellente pacchetto fclabels come interfaccia per i tuoi dati (piuttosto che i costruttori di modelli/tipi) per garantire che i tuoi dati siano sempre corretti .

Controllare la variante che supporta il guasto here e costruire l'obiettivo passando un setter e un getter che eseguono alcune convalide sul tipo di dati nella funzione lens.

Se avete bisogno di un po 'più complicato di segnalazione degli errori o roba del genere, date un'occhiata al implementation del Maybe variante di lens e definire il vostro obiettivo in termini di un'interfaccia astratta.