2013-08-17 10 views
6

Ho iniziato a utilizzare Yesod per sviluppare un piccolo progetto, questa è la prima volta che uso Haskell per fare qualcosa di reale. Questo codice che gestisce un modulo di registrazione funziona bene:Haskell: gestione delle eccezioni in non-IO monadi

postRegisterR :: Handler() 
postRegisterR = do email <- runInputPost $ ireq textField "email" 
        user <- runInputPost $ ireq textField "user" 
        pwd <- runInputPost $ ireq textField "pwd" 
        cpwd <- runInputPost $ ireq textField "cpwd" 
        if pwd == cpwd && isValidEmail email 
         then do 
         tryInsert email user pwd 
         setSession "user" user 
         redirectUltDest SessionR 
         else do 
         redirect HomeR 

tryInsert :: Text -> Text -> Text -> Handler() 
tryInsert email user pwd = do pwdbs <- liftIO $ hashedPwd pwd 
           _ <- runDB $ insert $ User email user pwdbs 
           return() 

Ora il problema è: se firmo in due volte con le stesse credenziali ottengo una InternalServerError. Questo è giusto, perché nella configurazione del mio modello c'è UniqueUser email username. Quindi mi piacerebbe prendere e gestire questo errore in qualche modo. Come posso farlo e, in generale, come funziona la gestione delle eccezioni in Haskell quando si hanno a che fare con le monade non-IO definite in una libreria o in un framework esterno?

PS: Ho letto il tutorial this, ma ciò è utile se state progettando una nuova libreria. Ho provato a utilizzare la funzione catch, ma ho ricevuto molti errori di tipo.

Modifica

Grazie Ankur, il codice ha lavorato con una piccola modifica, per eliminare questo errore:

Ambiguous type variable `e0' in the constraint: 
     (Exception e0) arising from a use of `catch' 
    Probable fix: add a type signature that fixes these type variable(s) 

codice:

tryInsert :: Text -> Text -> ByteString -> Handler Bool 
tryInsert email user pwd = HandlerT (\d -> catch (unHandlerT (runDB $ insert $ User email user pwd) d 
                >> return True) 
               (\(e :: SomeException) -> return False)) 

Con ScopedTypeVariables estensione abilitata

Edit 2

versione finale, dopo il suggerimento bennofs':

{-# LANGUAGE ScopedTypeVariables #-} 
import Control.Exception.Lifted (catch) 
import Control.Monad (void) 

postRegisterR :: Handler() 
postRegisterR = do email <- runInputPost $ ireq textField "email" 
        user <- runInputPost $ ireq textField "user" 
        pwd <- runInputPost $ ireq textField "pwd" 
        cpwd <- runInputPost $ ireq textField "cpwd" 
        if pwd == cpwd && isValidEmail email 
         then do 
         pwdbs <- liftIO $ hashedPwd pwd 
         success <- tryInsert email user pwdbs 
         case success of 
          True -> do setSession "user" user 
            redirectUltDest SessionR 
          False -> redirect HomeR 
         else do 
         redirect HomeR 

tryInsert :: Text -> Text -> ByteString -> Handler Bool 
tryInsert email user pwd = do void $ runDB $ insert $ User email user pwd 
           return True 
           `catch` (\(e :: SomeException) -> 
            do return False) 
+2

È possibile utilizzare [checkUnique] (http://hackage.haskell.org/packages/archive/persistent/0.3.1.3/doc/html/Database-Persist.html#v:checkUnique) per verificare se la chiave è unico prima di inserire ed evitare l'eccezione gestendo diversamente quella causa. – bennofs

+0

Umh ... non c'è checkUnique nelle versioni più recenti di Yesod, ma ho trovato [insertUnique] (http://hackage.haskell.org/packages/archive/persistent/latest/doc/html/Database-Persist-Class .html # v: insertUnique), grazie. Ad ogni modo sono ancora interessato alla gestione delle eccezioni. – andrebask

+1

Puoi usare l'estensione di linguaggio 'ScopedTypeVariables' e poi fare' (\ (e :: SomeException) -> return False) ' – Ankur

risposta

3

Si può provare qualcosa come indicato qui sotto, in fondo Handler è HandlerT che è trasformatore monade (non ho controllato digitare il codice qui sotto :))

tryInsert :: Text -> Text -> Text -> Handler Bool 
tryInsert email user pwd = HandlerT (\d -> do pwdbs <- hashedPwd pwd 
               catch (unHandlerT (runDB $ insert $ User email user pwdbs) d >> return True) 
                (\e -> return False)) 

E controllare il valore di bool restituito in caso di eccezione o meno.

7

C'è un pacchetto chiamato lifted-base, che fornisce anche una funzione di cattura più generico:

Control.Exception.Lifted.catch :: 
    (MonadBaseControl IO m, Exception e) 
    => m a   --^The computation to run 
    -> (e -> m a) --^Handler to invoke if an exception is raised 
    -> m a 

Esiste un'istanza MonadBaseControl IO Handler, quindi si può semplicemente utilizzare questa funzione:

{-# LANGUAGE ScopedTypeVariables #-} -- I think this is needed PatternSignatures. 
import Control.Exception.Lifted (catch) 
import Control.Monad (void) 

tryInsert :: Text -> Text -> Text -> Handler() 
tryInsert email user pwd = do 
    pwdbs <- liftIO $ hashedPwd pwd 
    (void $ runDB $ insert $ User email user pwdbs) `catch` \(e :: SomeException) -> do 
    -- Your exception handling goes code here. This code also lives in the Handler monad. 
    return() 
return() 

Un'altra possibilità è quella di utilizzare MonadCatchIO-mtl, che fornisce anche una funzione di cattura generica. Tuttavia MonadCatchIO-mtl non si baserà su GHC HEAD. Penso inoltre che l'utilizzo di insertUnique sia il modo più semplice per gestirlo.

+0

Grazie, questa è anche una buona soluzione, puoi evitare tutto ciò che riguarda Handler/unHandler. Sì, il metodo "insertUnique" è probabilmente la soluzione migliore in questo caso, ma in generale questa discussione sarà utile in futuro, non ho trovato molte informazioni chiare sull'argomento. – andrebask