2015-07-11 152 views
2

這是我之前發佈的後續內容。 MaybeT and Transactions in runDb從runDb中捕捉異常

我認爲這將是一件簡單的事情,但我一直試圖弄清楚這一點,並且還沒有取得很大進展。所以我以爲我會放棄和問!

我剛剛添加了一個try函數(從Control.Exception.Lifted)到我以前的代碼,我無法得到輸入檢查的代碼。像catchhandle等變體也有類似的問題。

eauth <- LiftIO (
      try(runDb $ do 
       ma <- runMaybeT $ do 
        valid <- ... 
       case ma of 
        Just a -> return a 
        Nothing -> liftIO $ throwIO MyException 
      ) :: IO (Either MyException Auth) 
     ) 
case eauth of 
    Right auth -> return auth 
    Left _  -> lift $ left err400 { errBody = "Could not create user"} 

runDb看起來像這樣(我也嘗試了變種,我刪除liftIO):

runDb query = do 
    pool <- asks getPool 
    liftIO $ runSqlPool query pool 

我得到這個錯誤:

No instance for (Control.Monad.Reader.Class.MonadReader Config IO) 
    arising from a use of ‘runDb’ 
In the expression: runDb 
In the first argument of ‘try’, namely 
    ‘(runDb 
    $ do { ma <- runMaybeT ... 

我的僕人處理程序內運行,我的退貨類型爲AppM Auth,其中

type AppM = ReaderT Config (EitherT ServantErr IO) 

我已經嘗試過許多提升組合,但似乎沒有幫助。我想我會藉此機會從頭開始弄清楚事情,並且我也打了一堵牆。如果有人能夠提出你是如何得出答案的,那對我來說將是非常有益的。

這一直是我的思維過程:

  1. 我看到runSqlConn :: MonadBaseControl IO m => SqlPersistT m a -> Connection -> m a
  2. 所以這似乎暗示這將是在IO單子,這意味着try應該工作
  3. 我想檢查的定義MonadBaseControl其中有class MonadBase b m => MonadBaseControl b m | m -> b。在這一點上我很困惑。這種功能依賴邏輯似乎是建議類型m規定了什麼b將是,但在前一個b被指定爲IO
  4. 我檢查了MonadBase,那也沒有給我任何線索。
  5. 我查了SqlPersistT,也沒有找到線索。
  6. 我將這個問題簡化爲一些非常簡單的問題,例如result <- liftIO (try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)),這很有效。所以我現在更困惑了。不是runDbIO工作,所以不應該是我的原代碼相同的東西工作?

我想我可以通過回溯來弄清楚這一點,但好像我的Haskell知識水平不足以解決問題的根源。欣賞人們是否可以逐步提供指針以達成正確的解決方案。

謝謝!

+1

是'LiftIO'一個錯字,應該是'liftIO'?另外'try'不能改變它正在運行的monad('runDB'沒有返回'IO',並且你試圖讓它返回'IO')。實際上,它看起來像'try'可能對'runDB'本身很好。 – Guvante

+1

您應該爲'runDb'提供一個類型簽名。 @ Guvante的評論和我的回答都應該明確指出'runDB'不會返回'IO'。既然你使用的異常只有一個值,'Maybe' /'MaybeT' monad對於你正在做的事情來說足夠強大。看到我答案的最後部分。如果你仍然有問題,你應該包含你在'runMaybeT'塊中的代碼。 –

+0

@Guvante,你是對的'LiftIO'是一個錯字,你的觀察也是正確的。因爲我的簽名,我最終走下了兔子洞。 – Ecognium

回答

3

try一般類型簽名:

(MonadBaseControl IO m, Exception e) => m a -> m (Either e a) 

try特殊類型的簽名(因爲它出現在你的代碼):

IO Auth -> IO (Either MyException Auth) 

所以,一元值是參數try有類型:

IO Auth 

Ev上面列出的東西,你可能已經明白了。如果我們看一下類型簽名爲您runDb,我們得到這樣的:

runDb :: (MonadReader Config m, MonadIO m) => SqlPersistT m a -> m a 

我有點猜,因爲你沒有提供類型簽名,但這是它可能是什麼。所以現在,問題應該更清楚一點。您正嘗試使用runDb爲應該在IO之內的東西創建一個monadic值。但IO不符合您需要的MonadReader Config實例。

爲了使錯誤更加清晰,我們讓runDb更單變。你可以給它這種類型的簽名:

type AppM = ReaderT Config (EitherT ServantErr IO) 
runDb :: SqlPersistT AppM a -> AppM a 

現在,如果你試圖編譯代碼,你會得到一個更好的錯誤。而是告訴你

No instance for (Control.Monad.Reader.Class.MonadReader Config IO) 

的它會告訴你,IO不匹配AppM(雖然它可能會擴大型代名詞)。實際上,這意味着你無法從IO中奇蹟般地獲得共享的數據庫連接池。你需要遍佈各處的ReaderT Config

我能想到會的最簡單的解決是停止使用的例外,他們沒有必要:

mauth <- runDb $ runMaybeT $ do 
      ... -- Same stuff you were doing earlier 
case mauth of 
    Just auth -> return auth 
    Nothing -> lift $ left err400 { errBody = "Could not create user"} 
+0

謝謝你的詳細回覆@安德魯。我在Reddit上發佈了這個帖子,並且有人提出了你和@Guvante提到的內容。簡短的故事是我爲'try'提供了類型簽名來抑制一個錯誤消息,但是之後會導致更多問題。只要將類型簽名移動到case表達式,直接刪除'liftIO'並直接使用'try'就行。 https://www.reddit.com/r/haskell/comments/3d5kdm/help_with_catching_exceptions/ – Ecognium

+0

@Ecognium這確實有用,但正如我試圖在我的最後一個代碼塊中解釋的那樣,使用'try'完全沒有必要。您正在將'Maybe'轉換爲'Exception',將'Exception'轉換爲'Either',然後進行模式匹配以返回到'Exception'。我的建議是僅僅使用'MaybeT'來提前終止,然後對結果進行模式匹配以將其變爲異常。如果你把你的功能的全部內容放在某個地方(也許在另一個問題上),那麼我可以告訴你我的意思更好。 –

+0

謝謝。我拋出異常的原因是這是'回滾'交易的唯一方法。我之前只是使用MaybeT,Nothing的值不回滾,所以需要'throwIO Exception'。當然,我的'MaybeT'中有很多插入語句,當然你不會從我的問題中知道。 – Ecognium