2013-08-17 17 views
6

Ich fing an, Yesod zu verwenden, um ein kleines Projekt zu entwickeln, das ist das erste Mal, dass ich Haskell verwende, um etwas wirklich zu tun. Dieser Code, der ein Anmeldeformular Griffe funktioniert:Haskell: Ausnahmebehandlung in Nicht-IO-Monaden

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() 

Nun das Problem ist: Wenn ich zweimal mit den gleichen Zugangsdaten loggen ich eine InternalServerError bekommen. Das ist richtig, denn in meiner Modellkonfiguration gibt es . Also möchte ich diesen Fehler auf irgendeine Weise erfassen und behandeln. Wie kann ich das im Allgemeinen tun und wie funktioniert die Ausnahmebehandlung in Haskell, wenn Sie mit Nicht-IO-Monaden arbeiten, die in einer externen Bibliothek oder einem externen Framework definiert sind?

PS: Ich lese this Tutorial, aber das ist nützlich, wenn Sie eine neue Bibliothek entwerfen. Ich habe versucht, die Catch-Funktion zu verwenden, aber ich habe viele Typfehler bekommen.

bearbeiten

Danke Ankur arbeitete Ihr Code mit einer kleinen Änderung, um diesen Fehler zu entfernen:

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) 

Code:

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)) 

Mit ScopedTypeVariables Erweiterung aktiviert

Edit 2

Endfassung nach bennofs' Hinweis:

{-# 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

Sie [checkUnique] (http://hackage.haskell.org/packages/archive/persistent/0.3.1.3/doc/html/Database-Persist.html#v:checkUnique) verwenden könnte der Schlüssel zu testen, ob ist vor dem Einfügen eindeutig, und vermeiden Sie die Ausnahme, indem Sie diesen Fall anders behandeln. – bennofs

+0

Umh ... es gibt kein checkUnique in den neueren Versionen von Yesod, aber ich fand [insertUnique] (http://hackage.haskell.org/packages/archive/persistent/latest/doc/html/Database-Persist-Class .html # v: insertUnique), danke. Trotzdem bin ich immer noch am Ausnahmehandling interessiert. – andrebask

+1

Sie können 'ScopedTypeVariables' Spracherweiterung verwenden und dann' (\ (e :: SomeException) -> False) ' – Ankur

Antwort

3

Sie so etwas wie unten gezeigt versuchen kann, im Grunde HandlerHandlerT ist die Monade Transformator ist (ich habe nicht den Code unten geprüft Typ :))

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)) 

Und überprüfen Sie den zurückgegebenen Bool-Wert, wenn es eine Ausnahme gab oder nicht.

7

Es ist ein Paket lifted-base genannt, was auch eine allgemeinere Fang-Funktion bietet:

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 

Es gibt eine Instanz MonadBaseControl IO Handler, so dass Sie nur diese Funktion nutzen zu können:

{-# 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() 

Eine andere Möglichkeit ist die Verwendung von MonadCatchIO-mtl, die auch eine generische Fangfunktion bietet. MonadCatchIO-mtl wird jedoch nicht auf GHC HEAD aufbauen. Ich denke auch immer noch, dass die Verwendung von insertUnique der sauberste Weg ist, damit umzugehen.

+0

Danke, das ist auch eine gute Lösung, können Sie all diese Handler/unHandler Sachen vermeiden. Ja, der 'insertUnique'-Weg ist wahrscheinlich die beste Lösung in diesem Fall, aber im Allgemeinen wird diese Diskussion in Zukunft nützlich sein, ich habe nicht viele klare Informationen zu dem Thema gefunden. – andrebask