2012-05-05 15 views
9

Nel seguente codice di Haskell:Come posso rilevare le eccezioni di lettura in Haskell?

data Cmd = 
    CmdExit | 
    CmdOther 
    deriving (Read, Show) 

guiString2Cmd s = 
    (return (read s :: Cmd)) `catch` \(e :: SomeException) -> return CmdExit 

Se lo faccio:

guiString2Cmd "CmdOther" 

tutto funziona bene. Tuttavia, se lo faccio:

guiString2Cmd "some wrong string" 

il codice va in crash invece di valutare CmdExit.

Come posso fare in modo che il codice gestisca l'eccezione anziché l'arresto anomalo?

+0

Utilizzando letture potrebbe essere una migliore idea in realtà ... – mbrodersen

+1

Sì, il migliore modo per gestire una pura eccezione è non generarlo in primo luogo. – dave4420

+1

Per la cronologia, le eccezioni (comprese quelle generate da 'read') possono essere rilevate solo nella monade IO, utilizzando le funzioni fornite in' Control.Exception'. Non entrerò in ulteriori dettagli perché "legge" è una soluzione migliore. –

risposta

13

utilizzare la funzione reads, che è totale, e avvolgere il caso fallimento come Maybe, in questo modo:

maybeRead :: Read a => String -> Maybe a 
maybeRead s = case reads s of 
    [(x, "")] -> Just x 
    _   -> Nothing 

maybeRead è piuttosto un modo versatile per fare l'analisi di sicurezza.

+4

Questa funzione è disponibile come ['Text.Read.readMaybe'] (http://www.haskell.org/ghc/docs/latest/html/libraries/base/Text-Read.html#v:readMaybe) in recenti versioni di 'base'. – hammar

2

Una soluzione è semplicemente utilizzare legge invece.

2

Io personalmente consigliamo utilizzando readMay dal pacchetto safe:

readMay :: Read a => String -> Maybe a 

allora o pattern-partita sul 'Forse un' risultato, l'uso 'forse', o anche utilizzare il 'Forse' monade per gestire il risultato.

1

Esiste un idioma di lettura all'interno di una monade:

readM :: (Monad m, Read a) => String -> m a 
readM s | [x] <- [x | (x, "") <- reads s] = return x 
     -- or @[x] <- [x | (x, _) <- reads s] = return [email protected] 
     -- to allow the garbage at the end of parsed string 
     | otherwise = fail $ "Failed to parse: \"" ++ s ++ "\"" 

E 'pericoloso per la IO monade:

> readM "CmdOther" :: IO Cmd 
CmdOther 
> readM "Cmd?Other" :: IO Cmd 
*** Exception: user error (Failed to parse: "Cmd?Other") 

perché fail genera un'eccezione IOError nel caso di IO, che però , può essere gestito:

*Main> (readM "Cmd?Other" :: IO Cmd) `catch` const (return CmdOther) 
CmdOther 

e sicuro nel caso di Maybe monade:

> readM "CmdOther" :: Maybe Cmd 
Just CmdOther 
> readM "Cmd?Other" :: Maybe Cmd 
Nothing 

perché fail è const Nothing in questo caso.

In ogni caso, se si desidera una funzione totale guiString2Cmd con una firma String -> Cmd si può scrivere come readM:

guiString2Cmd :: String -> Cmd 
guiString2Cmd s | [x] <- [x | (x, "") <- reads s] = x 
       | otherwise = CmdExit 

e poi:

> guiString2Cmd "CmdOther" 
CmdOther 
> guiString2Cmd "Cmd?Other" 
CmdExit 

approccio leggermente più generico.

Per * tipi:

class Failable0 t where 
    fail0 :: t 

readG0 :: (Failable0 t, Read t) => String -> t 
readG0 s | [x] <- [x | (x, "") <- reads s] = x 
     | otherwise = fail0 

poi:

instance Failable0 Cmd where 
    fail0 = CmdExit 

Per * -> * tipi:

class Failable f where 
    fail :: String -> f a 

class Functor f => Pointed f where 
    pure :: a -> f a 

readG :: (Failable f, Pointed f, Read a) => String -> f a 
readG s | [x] <- [x | (x, "") <- reads s] = pure x 
     | otherwise = fail $ "Failed to parse: \"" ++ s ++ "\"" 
+0

'fail' non è' error' nel caso di 'IO', genera un'eccezione' IOError'. Confronta 'fail" oh no! " \ 'catch \' (\ e -> print (e :: IOError)) 'con' errore "oh no!" \ 'catch \' (\ e -> print (e :: IOError)) ' –

+0

@benmachine right,' 'fail"? " 'catch' const (return"! ")' '=>' "!" 'e' 'error"? " 'catch' const (return"! ")' '=>' *** Eccezione:? '. Grazie per il chiarimento. – JJJ

Problemi correlati