Поймать исключение из 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)

Я пробовал много комбинаций подъема, но, похоже, не помогает. Я думал, что воспользуюсь этой возможностью, чтобы разобраться с вещами с нуля, и я также ударил стену. Если кто-то может подсказать, как вы пришли к ответу, это будет очень поучительно для меня.

Это был мой мыслительный процесс:

  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)) и это сработало. Так что я был еще более смущен в это время. не 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"}
Другие вопросы по тегам