2013-08-17 11 views
6

Zacząłem używać Yesod do opracowania małego projektu, po raz pierwszy używam Haskella do zrobienia czegoś prawdziwego. Ten kod, który obsługuje formularz rejestracyjny działa dobrze:Haskell: obsługa wyjątków w monadach bez IO

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

Teraz problem jest: czy mogę się zalogować dwukrotnie tych samych poświadczeń dostaję InternalServerError. To prawda, ponieważ w mojej konfiguracji modelu jest UniqueUser email username. Chciałbym złapać i poradzić sobie z tym błędem w jakiś sposób. Jak mogę to zrobić i, ogólnie, jak obsługa wyjątków działa w Haskell, kiedy masz do czynienia z monadami nie-IO zdefiniowanymi w zewnętrznej bibliotece lub strukturze?

PS: Przeczytałem samouczek this, ale jest to przydatne, jeśli projektujesz nową bibliotekę. Próbowałem użyć funkcji catch, ale mam wiele błędów typu.

Edit

Dziękuję Ankur, kod pracował z małą modyfikacją, aby usunąć ten błąd:

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) 

Kod:

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

Z ScopedTypeVariables przedłużenie włączoną

Edycja 2

wersja końcowa, po nutą 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

Możesz użyć [checkUnique] (http://hackage.haskell.org/packages/archive/persistent/0.3.1.3/doc/html/Database-Persist.html#v:checkUnique), aby sprawdzić, czy klucz jest unikalny przed wstawieniem, i uniknij wyjątku, traktując ten przypadek inaczej. – bennofs

+0

Umh ... nie ma checkUnique w nowszych wersjach Yesod, ale znalazłem [insertUnique] (http://hackage.haskell.org/packages/archive/persistent/latest/doc/html/Database-Persist-Class .html # v: insertUnique), dziękuję. W każdym razie nadal jestem zainteresowany obsługą wyjątków. – andrebask

+1

Możesz użyć rozszerzenia 'ScopedTypeVariables', a następnie wykonaj' (\ (e :: SomeException) -> return False) ' – Ankur

Odpowiedz

3

Można spróbować coś jak pokazano poniżej, w zasadzie Handler jest HandlerT czyli monada transformator (nie mam typem sprawdzana poniższy kod :))

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

I sprawdź zwróconą wartość bool, jeśli wystąpił wyjątek lub nie.

7

Jest to pakiet o nazwie lifted-base, który zapewnia również bardziej ogólną funkcję zbiornika:

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 

istnieje wystąpienie MonadBaseControl IO Handler, więc można po prostu skorzystać z tej funkcji:

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

Inną możliwością jest użycie MonadCatchIO-mtl, która zapewnia również ogólną funkcję catch. MonadCatchIO-mtl nie będzie jednak zbudowany na GHC HEAD. Nadal uważam, że używanie insertUnique jest najczystszym sposobem na poradzenie sobie z tym.

+0

Dzięki, jest to również dobre rozwiązanie, możesz uniknąć wszystkich rzeczy związanych z Handler/unHandler. Tak, metoda 'insertUnique' jest prawdopodobnie najlepszym rozwiązaniem w tym przypadku, ale generalnie ta dyskusja przyda się w przyszłości, nie znalazłem zbyt jasnych informacji na ten temat. – andrebask