从 runDb 捕获异常
Posted
技术标签:
【中文标题】从 runDb 捕获异常【英文标题】:Catching an Exception from runDb 【发布时间】:2015-07-11 17:30:17 【问题描述】:这是我之前帖子的后续。 MaybeT and Transactions in runDb
我认为这将是一件简单的事情,但我已经尝试了一天多,但仍然没有取得太大进展。所以想我会放弃并问!
我刚刚在我之前的代码中添加了一个try
函数(来自Control.Exception.Lifted
),但我无法获取代码来进行类型检查。 catch
和 handle
等变体也有类似的问题。
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)
我尝试了许多举重组合,但似乎没有帮助。我想我会借此机会从头开始弄清楚事情,我也碰壁了。如果有人可以建议您如何得出答案,那对我来说将是非常有启发性的。
这是我的思考过程:
-
我看到
runSqlConn :: MonadBaseControl IO m => SqlPersistT m a -> Connection -> m a
所以这似乎暗示它将在 IO
monad 中,这意味着 try
应该可以工作
我想检查MonadBaseControl
的定义,它有class MonadBase b m => MonadBaseControl b m | m -> b
。在这一点上,我很困惑。这个函数依赖逻辑似乎是建议类型m
指示b
将是什么,但在前一个b
被指定为IO
。
我检查了MonadBase
,这也没有给我任何线索。
我检查了SqlPersistT
,也没有任何线索。
我将问题简化为非常简单的问题,例如result <- liftIO (try (evaluate (5 `div` 0)) :: IO (Either SomeException Int))
,并且成功了。所以这个时候我就更加迷茫了。 runDb
不能在 IO
中工作,所以我的原始代码不应该同样工作吗?
我认为我可以通过回溯来解决这个问题,但我的 Haskell 知识水平似乎不足以解决问题的根源。欣赏人们是否可以提供逐步指导以得出正确的解决方案。
谢谢!
【问题讨论】:
LiftIO
是不是错字,应该是liftIO
? try
也无法更改它正在运行的 monad(runDB
没有返回 IO
,而您正试图让它返回 IO
)。事实上,看起来try
可能对runDB
本身也有效。
您应该为runDb
提供类型签名。 @Guvante 的评论和我的回答都应该清楚地表明 runDB
不会返回 IO
。由于您使用的异常只有一个值,因此 Maybe
/MaybeT
monad 对于您正在做的事情来说已经足够强大了。请参阅我答案的最后一部分。如果您仍然遇到问题,您应该在 runMaybeT
块中包含您省略的代码。
@Guvante,你说得对,LiftIO
是一个错字,你的观察也是正确的。由于我的类型签名,我最终陷入了兔子洞。
【参考方案1】:
try
的一般类型签名:
(MonadBaseControl IO m, Exception e) => m a -> m (Either e a)
try
的专用类型签名(如您的代码中所示):
IO Auth -> IO (Either MyException Auth)
因此,作为 try
参数的一元值具有以下类型:
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)
它会告诉你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"
【讨论】:
感谢@Andrew 的详细回复。我也在 Reddit 上发布了这个,有人建议你和@Guvante 也提到过。简短的故事是我为try
提供了类型签名来抑制一条错误消息,但它会在以后导致更多问题。只要我将类型签名移至 case 表达式,只需删除 liftIO
并直接使用 try
即可。 reddit.com/r/haskell/comments/3d5kdm/…
@Ecognium 确实有效,但正如我在上一个代码块中试图解释的那样,您完全没有必要使用 try
。您正在将Maybe
转换为Exception
,将Exception
捕获为Either
,然后进行模式匹配以返回Exception
。我的建议是只使用MaybeT
提前终止,然后对其结果进行模式匹配以将其变为异常。如果您将函数的全部内容放在某个地方(可能是另一个问题),那么我可以更好地向您展示我的意思。
谢谢。我抛出异常的原因是这是“回滚”事务的唯一方法。我之前只是使用 MaybeT,Nothing
值不会回滚,所以需要throwIO Exception
。我的MaybeT
中有一堆插入语句,当然你不会从我的问题中知道。
作为替代方案,还有transactionUndo,它可以在不使用特殊异常处理行为的情况下回滚事务。以防万一您需要它。
哦,太好了。我根本不知道。我会尝试使用它而不是 throwIO
并且可能会使事情变得更清洁。以上是关于从 runDb 捕获异常的主要内容,如果未能解决你的问题,请参考以下文章