Поймать исключение из runDb
Это продолжение моего предыдущего поста. MaybeT и транзакции в 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
монада, что означает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 недостаточно, чтобы понять причину проблемы. Цените, если люди могут предоставить пошаговые указатели, чтобы прийти к правильному решению.
Спасибо!
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"}