从 runDb 捕获异常
Catching an Exception from runDb
这是我之前 post 的后续。
我认为这将是一件简单的事情,但我已经尝试解决这个问题一天多了,但仍然没有取得太大进展。所以想我会放弃并问!
我刚刚在我以前的代码中添加了一个 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 ...
我 运行 在仆人处理程序中,我的 return 类型是 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 知识水平不足以找到问题的根源。感谢人们是否可以提供逐步指导以找到正确的解决方案。
谢谢!
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)
它会告诉您 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"}
这是我之前 post 的后续。
我认为这将是一件简单的事情,但我已经尝试解决这个问题一天多了,但仍然没有取得太大进展。所以想我会放弃并问!
我刚刚在我以前的代码中添加了一个 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 ...
我 运行 在仆人处理程序中,我的 return 类型是 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 知识水平不足以找到问题的根源。感谢人们是否可以提供逐步指导以找到正确的解决方案。
谢谢!
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)
它会告诉您 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"}