2015-07-11 91 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