6

我开始使用 Yesod 开发一个小项目,这是我第一次使用 Haskell 做一些真正的事情。此处理注册表单的代码工作正常:

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 monad 时,我该怎么做?一般来说,Haskell 中的异常处理是如何工作的?

PS:我读过这个教程,但是如果你正在设计一个新的库,这很有用。我尝试使用 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)
4

2 回答 2

7

有一个名为lift-base的包,它还提供了一个更通用的 catch 函数:

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 Handler,所以你可以使用这个函数:

{-# 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 上。我也仍然认为 usinginsertUnique是处理这个问题的最干净的方法。

于 2013-08-17T17:04:58.560 回答
3

您可以尝试如下所示的方法,基本上HandlerHandlerTmonad 转换器(我没有检查下面的代码 :))

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

并检查返回的布尔值是否有异常。

于 2013-08-17T13:31:56.917 回答