Haskell:非IO monad中的异常处理

and*_*ask 6 monads haskell exception-handling yesod

我开始使用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 ()
Run Code Online (Sandbox Code Playgroud)

现在的问题是:如果我使用相同的凭据登录两次,我会得到一个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)
Run Code Online (Sandbox Code Playgroud)

码:

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))
Run Code Online (Sandbox Code Playgroud)

随着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)
Run Code Online (Sandbox Code Playgroud)

ben*_*ofs 7

有一个名为lifted-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
Run Code Online (Sandbox Code Playgroud)

有一个实例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 ()
Run Code Online (Sandbox Code Playgroud)

另一种可能性是使用MonadCatchIO-mtl,它也提供了通用的catch函数.MonadCatchIO-mtl不会建立在GHC HEAD上.我还认为使用insertUnique是最干净的方法来处理这个问题.


Ank*_*kur 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))
Run Code Online (Sandbox Code Playgroud)

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