从 runDb 捕获异常

Catching an Exception from runDb

这是我之前 post 的后续。

我认为这将是一件简单的事情,但我已经尝试解决这个问题一天多了,但仍然没有取得太大进展。所以想我会放弃并问!

我刚刚在我以前的代码中添加了一个 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 ... 

我 运行 在仆人处理程序中,我的 return 类型是 AppM Auth 其中

 type AppM = ReaderT Config (EitherT ServantErr IO)

我尝试了多种提拉组合,但似乎都没有用。我想我会借此机会从头开始弄清楚事情,但我也碰壁了。如果有人可以建议您是如何得出答案的,那对我来说将非常有启发性。

这是我的思考过程:

  1. 我明白了runSqlConn :: MonadBaseControl IO m => SqlPersistT m a -> Connection -> m a
  2. 所以这似乎暗示它将在 IO monad 中,这意味着 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 知识水平不足以找到问题的根源。感谢人们是否可以提供逐步指导以找到正确的解决方案。

谢谢!

try 的通用类型签名:

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

try 的专用类型签名(如您的代码中所示):

IO Auth -> IO (Either MyException Auth)

因此,作为 try 参数的 monadic 值具有类型:

IO Auth

上面列出的一切,你可能已经明白了。如果我们查看 runDb 的类型签名,我们会得到:

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

我不得不猜测,因为您没有提供类型签名,但可能就是这样。那么现在,问题应该清楚一些了。您正在尝试使用 runDb 为应该在 IO 中的内容创建单子值。但是 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)

它会告诉您 IOAppM 不匹配(尽管它可能会扩展类型同义词)。实际上,这意味着您无法从 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"}