2013-08-17 46 views
6

我開始使用Yesod開發一個小項目,這是我第一次使用Haskell來做一些真正的事情。 此代碼來處理登記表工作正常:Haskell:非IO monads中的異常處理

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

現在的問題是:如果我用相同的憑證登入兩次我得到一個InternalServerError。這是對的,因爲在我的模型配置中有UniqueUser email username。所以我想以某種方式捕捉並處理這個錯誤。我該怎麼做,一般來說,當你處理外部庫或框架中定義的非IO monads時,如何在Haskell中執行異常處理?

PS:我讀了this教程,但是如果您正在設計一個新庫,那麼這很有用。我嘗試使用catch函數,但是我遇到了很多類型錯誤。

編輯

謝謝ANKUR,你的代碼稍加修改的工作,消除這種誤差:

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) 

代碼:

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

隨着ScopedTypeVariables擴展啓用

編輯2

最終版本,之後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

您可以使用[checkUnique](http://hackage.haskell.org/packages/archive/persistent/0.3.1.3/doc/html/Database-Persist.html#v:checkUnique)來測試密鑰是否在插入之前是唯一的,並通過不同的方式處理該情況來避免異常。 – bennofs

+0

嗯......在較新版本的Yesod中沒有checkUnique,但是我發現[insertUnique](http://hackage.haskell.org/packages/archive/persistent/latest/doc/html/Database-Persist-Class .html#v:insertUnique),謝謝。無論如何,我仍然對異常處理感興趣。 – andrebask

+1

您可以使用'ScopedTypeVariables'語言擴展,然後執行'(\(e :: SomeException) - > return False)' – Ankur

回答

3

你可以嘗試一些如下所示,基本上HandlerHandlerT這是單子轉換(我還沒有類型檢查下面的代碼:))

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

並檢查返回的bool值是否有異常。

7

有一個叫lifted-base包,這也提供了一個更通用的捕捉功能:

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 

存在一個實例MonadBaseControl IO處理程序,所以你可以使用此功能:

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

另一種可能是使用MonadCatchIO-mtl,它也提供了一個通用的catch函數。 MonadCatchIO-mtl不會在GHC HEAD上構建。我還認爲使用insertUnique是處理這個問題的最簡單的方法。

+0

謝謝,這也是一個很好的解決方案,你可以避免所有Handler/unHandler的東西。是的,在這種情況下,'insertUnique'方式可能是最好的解決方案,但總的來說,這個討論將來會很有用,但我沒有找到關於該主題的很多明確信息。 – andrebask