Haskell: exception handling in non-MO monads

I started using Yesod to develop a small project, this is the first time I use Haskell to create something real. This code that processes the registration form works fine:

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

Now the problem is that if I log in to the account twice with the same credentials, I get an InternalServerError . This is correct, because in my model configuration there is UniqueUser email username . So I would like to somehow catch and handle this error. How can I do this and, in general, how does Haskell exception handling work when you are dealing with non-IO monads defined in an external library or framework?

PS: I read , but it is useful if you are developing a new library. I tried using the catch function, but I got a lot of type errors.

Edit

Thanks Ankur, your code worked with a small modification to remove this error:

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

With ScopedTypeVariables Extension ScopedTypeVariables

Edit 2

The final version, after a hint of 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) 
+6
source share
2 answers

You can try something like the one shown below, basically Handler is HandlerT , which is a monad transformer (I did not check the code below :))

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

And check the return value of bool if there was an exception or not.

+3
source

There is a package called lifted-base , which also provides a more general catch function:

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

There is an instance of MonoBaseControl IO Handler, so you can just use this function:

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

Another possibility is to use MonadCatchIO-mtl , which also provides a generic catch function. However, MonadCatchIO-mtl will not be based on the GHC HEAD. I also find that using insertUnique is the cleanest way to handle this.

+7
source

Source: https://habr.com/ru/post/951926/


All Articles